Compare commits
21
Commits
f0b8b3051a
...
endpoints
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea0e4eb018 | ||
|
|
9ebbafb840 | ||
|
|
f3863c785d | ||
|
|
9874eac65d | ||
|
|
2b8edd05e3 | ||
|
|
edbae6defb | ||
|
|
b3e884336d | ||
|
|
1b0ceb8ea9 | ||
|
|
d779e4152e | ||
|
|
9e611cb79c | ||
|
|
b99ebf657c | ||
|
|
99ab5b2ce6 | ||
|
|
a15b25248d | ||
|
|
66f720a20b | ||
|
|
81c111c3c3 | ||
|
|
415aa30cf7 | ||
|
|
72ea20a2de | ||
|
|
15661ce8cb | ||
|
|
e274c33592 | ||
|
|
a218b19875 | ||
|
|
0a1378fe25 |
@@ -14,9 +14,9 @@ public sealed class GetApplicantInviteEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/communications/invites/applicant/{userId:Guid}", async (Guid userId, ICommunicationsService service,
|
builder.MapGet("api/communications/invites/applicant/{applicantUserId:Guid}", async (Guid applicantUserId, ICommunicationsService service,
|
||||||
HttpContext context, CancellationToken cancellationToken = default) =>
|
HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetApplicantInviteAsync(userId, cancellationToken) switch
|
await service.GetApplicantInviteAsync(applicantUserId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var invite } => Results.Ok(invite),
|
{ IsSuccess: true, Value: var invite } => Results.Ok(invite),
|
||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Communications;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetApplicantInvitesEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/communications/invites/{applicantUserId:Guid}", async (Guid applicantUserId, ICommunicationsService service,
|
||||||
|
HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetApplicantInvitesAsync(applicantUserId, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var invite } => Results.Ok(invite),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems()),
|
||||||
|
})
|
||||||
|
.WithDescription("Gets an applicant's invite list")
|
||||||
|
.WithName(typeof(GetApplicantInvitesEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Invite[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Communications)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,14 +23,15 @@ public sealed class GetIndicatorEndpoint : IEndpoint
|
|||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get a specific indicator.")
|
.WithDescription("Get a specific indicator.")
|
||||||
.WithName(typeof(GetIndicatorEndpoint).ToEndpointName())
|
.WithName(typeof(GetIndicatorEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Indicator>(StatusCodes.Status200OK)
|
.Produces<Indicator>(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Evidence);
|
.WithTags(EndpointTags.Evidence)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public class GetIndicatorsEndpoint : IEndpoint
|
|||||||
.Produces<Indicator[]>(StatusCodes.Status200OK)
|
.Produces<Indicator[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Evidence);
|
.WithTags(EndpointTags.Evidence)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class GetProjectEvidenceEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/evidence/milestone/{projectId}", async (string projectId, IEvidenceService service,
|
builder.MapGet("api/evidence/{projectId}", async (string projectId, IEvidenceService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch
|
await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class ApproveDisbursementEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/execution/disbursements/{disbursementId}/approve", async (string disbursementId,
|
builder.MapPost("api/execution/disbursements/{disbursementId}/approve", async (string disbursementId,
|
||||||
ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
IExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken) switch
|
await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Services.Execution;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateDisbursementEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request,
|
builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request,
|
||||||
ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
IExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Services.Execution;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -13,7 +14,7 @@ public class CreateRiskEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/execution/risks", async (CreateRiskRequest request, ExecutionService service,
|
builder.MapPost("api/execution/risks", async (CreateRiskRequest request, IExecutionService service,
|
||||||
HttpContext context, CancellationToken cancellationToken = default) =>
|
HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public sealed class GetDisbursementEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/execution/disbursements/{disbursementId}", async (string disbursementId, ExecutionService service,
|
builder.MapGet("api/execution/disbursements/{disbursementId}", async (string disbursementId, IExecutionService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetDisbursementAsync(disbursementIdHash: disbursementId, cancellationToken) switch
|
await service.GetDisbursementAsync(disbursementIdHash: disbursementId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -31,6 +31,7 @@ public sealed class GetDisbursementEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Execution);
|
.WithTags(EndpointTags.Execution)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetDisbursementsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/execution/disbursements", async (ExecutionService service,
|
builder.MapGet("api/execution/disbursements", async (IExecutionService service,
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -28,6 +28,7 @@ public class GetDisbursementsEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Execution);
|
.WithTags(EndpointTags.Execution)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public sealed class GetRiskEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/execution/risks/{riskId}", async (string riskId, ExecutionService service,
|
builder.MapGet("api/execution/risks/{riskId}", async (string riskId, IExecutionService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetRiskAsync(riskIdHash: riskId, cancellationToken) switch
|
await service.GetRiskAsync(riskIdHash: riskId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,7 @@ public sealed class GetRiskEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Execution);
|
.WithTags(EndpointTags.Execution)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -13,7 +13,7 @@ public class GetRisksEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/execution/risks", async (ExecutionService service,
|
builder.MapGet("api/execution/risks", async (IExecutionService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -26,6 +26,7 @@ public class GetRisksEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status200OK)
|
.Produces(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Execution);
|
.WithTags(EndpointTags.Execution)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Services.Execution;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
@@ -14,7 +15,7 @@ public sealed class UpdateDisbursementEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/execution/disbursements", async (UpdateDisbursementRequest request, HttpContext context,
|
builder.MapPut("api/execution/disbursements", async (UpdateDisbursementRequest request, HttpContext context,
|
||||||
ExecutionService service, CancellationToken cancellationToken = default) =>
|
IExecutionService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateDisbursementAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
await service.UpdateDisbursementAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Execution;
|
using PostFundManagement.Application.Services.Execution;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
@@ -14,7 +15,7 @@ public sealed class UpdateRiskEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/execution/risks", async (UpdateRiskRequest request,
|
builder.MapPut("api/execution/risks", async (UpdateRiskRequest request,
|
||||||
ExecutionService service, CancellationToken cancellationToken = default) =>
|
IExecutionService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateRiskAsync(request, cancellationToken) switch
|
await service.UpdateRiskAsync(request, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
using PostFundManagement.Application.Services.Governance;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public sealed class CreateOrganisationEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, HttpContext context,
|
builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, HttpContext context,
|
||||||
GovernanceService service, CancellationToken cancellationToken = default) =>
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Governance;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Governance;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class CreatePortfolioEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapPost("api/governance/organisations/portfolio", async (CreatePortfolioRequest request, HttpContext context,
|
||||||
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreatePortfolioAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(r.Errors.ToProblems()),
|
||||||
|
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Create a new organization portfolio")
|
||||||
|
.WithName(typeof(CreatePortfolioEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces<Problems>(StatusCodes.Status404NotFound)
|
||||||
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.WithTags(EndpointTags.Governance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
using PostFundManagement.Application.Services.Governance;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -12,7 +13,7 @@ public class DashboardEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/dashboard", async (GovernanceService service,
|
builder.MapGet("api/governance/dashboard", async (IGovernanceService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetDashboardAsync(cancellationToken) switch
|
await service.GetDashboardAsync(cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -25,6 +26,7 @@ public class DashboardEndpoint : IEndpoint
|
|||||||
.Produces<Dashboard>(StatusCodes.Status200OK)
|
.Produces<Dashboard>(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Governance);
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
using PostFundManagement.Domain.Security;
|
using PostFundManagement.Domain.Security;
|
||||||
@@ -13,19 +14,22 @@ public class GetInstrumentsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/instruments", async (GovernanceService service,
|
builder.MapGet("api/governance/instruments/{programmeId}", async (string programmeId, IGovernanceService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetInstrumentsAsync(cancellationToken) switch
|
await service.GetInstrumentsAsync(programmeId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs),
|
{ IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems()),
|
var r => Results.InternalServerError(r.Errors.ToProblems()),
|
||||||
})
|
})
|
||||||
.WithDescription("Get all funding instruments")
|
.WithDescription("Get all funding instruments")
|
||||||
.WithName(typeof(GetInstrumentsEndpoint).ToEndpointName())
|
.WithName(typeof(GetInstrumentsEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Instrument[]>(StatusCodes.Status200OK)
|
.Produces<Instrument[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Governance);
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -15,10 +15,10 @@ public class GetOrganisationEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/organisations/{organisationId}", async (string organisationId,
|
builder.MapGet("api/governance/organisations/{organisationId}", async (string organisationId,
|
||||||
GovernanceService service, CancellationToken cancellationToken = default) =>
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetOrganisationAsync(organisationId, cancellationToken) switch
|
await service.GetOrganisationAsync(organisationId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var organisation } => Results.Ok(organisationId),
|
{ IsSuccess: true, Value: var organisation } => Results.Ok(organisation),
|
||||||
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
@@ -31,6 +31,7 @@ public class GetOrganisationEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Governance);
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetOrganisationsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/organisations", async (GovernanceService service,
|
builder.MapGet("api/governance/organisations", async (IGovernanceService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ public class GetOrganisationsEndpoint : IEndpoint
|
|||||||
.Produces<Organisation[]>(StatusCodes.Status200OK)
|
.Produces<Organisation[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Governance);
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Governance;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetPortfolioEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/governance/portfolios/{portfolioId}", async (string portfolioId, IGovernanceService service,
|
||||||
|
CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetPortfolioAsync(portfolioIdHash: portfolioId, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var portfolio } => Results.Ok(portfolio),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Get a specific portfolios")
|
||||||
|
.WithName(typeof(GetPortfolioEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Portfolio[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces<Portfolio[]>(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Governance;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetPortfolioProgrammesEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/governance/portfolios/{portfolioId}/programmes", async (string portfolioId, IGovernanceService service,
|
||||||
|
CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetPortfolioProgrammesAsync(portfolioId, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Get a programme's portfolios")
|
||||||
|
.WithName(typeof(GetPortfolioProgrammesEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Programme[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
using PostFundManagement.Domain.Security;
|
using PostFundManagement.Domain.Security;
|
||||||
@@ -13,19 +14,22 @@ public class GetPortfoliosEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/portfolios", async (GovernanceService service,
|
builder.MapGet("api/governance/organisations/{organisationId}/portfolio", async (string organisationId,
|
||||||
CancellationToken cancellationToken = default) =>
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetPorfoliosAsync(cancellationToken) switch
|
await service.GetPorfoliosAsync(organisationId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
|
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get all portfolios")
|
.WithDescription("Get all portfolios")
|
||||||
.WithName(typeof(GetPortfoliosEndpoint).ToEndpointName())
|
.WithName(typeof(GetPortfoliosEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Portfolio[]>(StatusCodes.Status200OK)
|
.Produces<Portfolio[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Governance);
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
using PostFundManagement.Domain.Security;
|
using PostFundManagement.Domain.Security;
|
||||||
@@ -13,19 +14,22 @@ public class GetProgrammesEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/governance/programmes", async (GovernanceService service,
|
builder.MapGet("api/governance/programmes/{portfolioId}", async (string portfolioId, IGovernanceService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProgrammesAsync(cancellationToken) switch
|
await service.GetProgrammesAsync(portfolioId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var programmes } => Results.Ok(programmes),
|
{ IsSuccess: true, Value: var programmes } => Results.Ok(programmes),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get all programmes")
|
.WithDescription("Get all programmes")
|
||||||
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName())
|
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Programme[]>(StatusCodes.Status200OK)
|
.Produces<Programme[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Governance);
|
.WithTags(EndpointTags.Governance)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Governance;
|
using PostFundManagement.Application.Services.Governance;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
@@ -14,7 +15,7 @@ public sealed class UpdateOrganisationEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request,
|
builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request,
|
||||||
GovernanceService service, CancellationToken cancellationToken = default) =>
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateOrganisationAsync(request, cancellationToken) switch
|
await service.UpdateOrganisationAsync(request, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Governance;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Governance;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class UpdatePortfolioEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapPut("api/governance/organisations/portfolio", async (UpdatePortfolioRequest request, HttpContext context,
|
||||||
|
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdatePortfolioAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
|
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Update an existing organization portfolio")
|
||||||
|
.WithName(typeof(UpdatePortfolioEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Governance);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
using PostFundManagement.Application.Services.Grants;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateAwardEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/grants/awards", async (CreateAwardRequest request, HttpContext context,
|
builder.MapPost("api/grants/awards", async (CreateAwardRequest request, HttpContext context,
|
||||||
GrantsService service, CancellationToken cancellationToken = default) =>
|
IGrantsService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
using PostFundManagement.Application.Services.Grants;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateBudgetEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, HttpContext context,
|
builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, HttpContext context,
|
||||||
GrantsService service, CancellationToken cancellationToken = default) =>
|
IGrantsService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
@@ -23,15 +24,15 @@ public class CreateBudgetEndpoint : IEndpoint
|
|||||||
var r when r.HasError<CreateError>() => Results.BadRequest(),
|
var r when r.HasError<CreateError>() => Results.BadRequest(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Create a new budget line (M05)")
|
.WithDescription("Create a new budget line (M05)")
|
||||||
.WithName(typeof(CreateBudgetEndpoint).ToEndpointName())
|
.WithName(typeof(CreateBudgetEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces(StatusCodes.Status200OK)
|
.Produces(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces(StatusCodes.Status400BadRequest)
|
.Produces(StatusCodes.Status400BadRequest)
|
||||||
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
using PostFundManagement.Application.Services.Grants;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateContractEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/grants/contracts", async (CreateContractRequest request, HttpContext context,
|
builder.MapPost("api/grants/contracts", async (CreateContractRequest request, HttpContext context,
|
||||||
GrantsService service, CancellationToken cancellationToken = default) =>
|
IGrantsService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
using PostFundManagement.Application.Services.Grants;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class ExecuteContractEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/grants/contracts/execute", async (ExecuteContractRequest request, HttpContext context,
|
builder.MapPost("api/grants/contracts/execute", async (ExecuteContractRequest request, HttpContext context,
|
||||||
GrantsService service, CancellationToken cancellationToken = default) =>
|
IGrantsService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetAwardEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/awards/{awardId}", async (string awardId, GrantsService service,
|
builder.MapGet("api/grants/awards/{awardId}", async (string awardId, IGrantsService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetAwardAsync(awardId, cancellationToken) switch
|
await service.GetAwardAsync(awardId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -23,14 +23,15 @@ public class GetAwardEndpoint : IEndpoint
|
|||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get award by ID")
|
.WithDescription("Get award by ID")
|
||||||
.WithName(typeof(GetAwardEndpoint).ToEndpointName())
|
.WithName(typeof(GetAwardEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Award>(StatusCodes.Status200OK)
|
.Produces<Award>(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,19 +14,20 @@ public class GetAwardsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/awards", async (GrantsService service,
|
builder.MapGet("api/grants/awards", async (IGrantsService service,
|
||||||
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var awards } => Results.Ok(awards),
|
{ IsSuccess: true, Value: var awards } => Results.Ok(awards),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get a list of awards")
|
.WithDescription("Get a list of awards")
|
||||||
.WithName(typeof(GetAwardsEndpoint).ToEndpointName())
|
.WithName(typeof(GetAwardsEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Award[]>(StatusCodes.Status200OK)
|
.Produces<Award[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetBudgetEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/budgets/{budgetId}", async (string budgetId, GrantsService service,
|
builder.MapGet("api/grants/budgets/{budgetId}", async (string budgetId, IGrantsService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetBudgetAsync(budgetId, cancellationToken) switch
|
await service.GetBudgetAsync(budgetId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -23,14 +23,15 @@ public class GetBudgetEndpoint : IEndpoint
|
|||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get budget by ID")
|
.WithDescription("Get budget by ID")
|
||||||
.WithName(typeof(GetBudgetEndpoint).ToEndpointName())
|
.WithName(typeof(GetBudgetEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Budget>(StatusCodes.Status200OK)
|
.Produces<Budget>(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,19 +14,20 @@ public class GetBudgetsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/budgets", async (GrantsService service,
|
builder.MapGet("api/grants/budgets", async (IGrantsService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var budgets } => Results.Ok(budgets),
|
{ IsSuccess: true, Value: var budgets } => Results.Ok(budgets),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get all budgets")
|
.WithDescription("Get all budgets")
|
||||||
.WithName(typeof(GetBudgetsEndpoint).ToEndpointName())
|
.WithName(typeof(GetBudgetsEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Budget[]>(StatusCodes.Status200OK)
|
.Produces<Budget[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetContractEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/contracts/{contractId}", async (string contractId, GrantsService service,
|
builder.MapGet("api/grants/contracts/{contractId}", async (string contractId, IGrantsService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetContractAsync(contractId, cancellationToken) switch
|
await service.GetContractAsync(contractId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -23,14 +23,15 @@ public class GetContractEndpoint : IEndpoint
|
|||||||
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Get contract by ID")
|
.WithDescription("Get contract by ID")
|
||||||
.WithName(typeof(GetContractEndpoint).ToEndpointName())
|
.WithName(typeof(GetContractEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Contract>(StatusCodes.Status200OK)
|
.Produces<Contract>(StatusCodes.Status200OK)
|
||||||
.Produces(StatusCodes.Status404NotFound)
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Grants;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetContractsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/grants/contracts", async (GrantsService service,
|
builder.MapGet("api/grants/contracts", async (IGrantsService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ public class GetContractsEndpoint : IEndpoint
|
|||||||
.Produces<Contract[]>(StatusCodes.Status200OK)
|
.Produces<Contract[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Grants);
|
.WithTags(EndpointTags.Grants)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Services.Projects;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateActivityEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/projects/activities", async (CreateActivityRequest request,
|
builder.MapPost("api/projects/activities", async (CreateActivityRequest request,
|
||||||
HttpContext context, ProjectService service, CancellationToken cancellationToken = default) =>
|
HttpContext context, IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } result => Results.Ok(),
|
{ IsSuccess: true } result => Results.Ok(),
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Projects;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Projects;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class CreateProgrammeEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapPost("api/projects/programme", async (CreateProgrammeRequest request, HttpContext context,
|
||||||
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r when r.HasError<CreateError>() => Results.BadRequest(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Create programme")
|
||||||
|
.WithName(typeof(CreateProgrammeEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Projects);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Services.Projects;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class CreateProjectEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPost("api/projects", async (CreateProjectRequest request, HttpContext context,
|
builder.MapPost("api/projects", async (CreateProjectRequest request, HttpContext context,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } result => Results.Ok(),
|
{ IsSuccess: true } result => Results.Ok(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetActivitiesEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/{projectId}/activities", async (string projectId,
|
builder.MapGet("api/projects/{projectId}/activities", async (string projectId,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetActivitiesAsync(projectId, cancellationToken) switch
|
await service.GetActivitiesAsync(projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var activities } => Results.Ok(activities),
|
{ IsSuccess: true, Value: var activities } => Results.Ok(activities),
|
||||||
@@ -28,6 +28,7 @@ public class GetActivitiesEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetActivityEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/activities/{activityId}", async (string activityId,
|
builder.MapGet("api/projects/activities/{activityId}", async (string activityId,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetActivityAsync(activityId, cancellationToken) switch
|
await service.GetActivityAsync(activityId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var activity } => Results.Ok(activity),
|
{ IsSuccess: true, Value: var activity } => Results.Ok(activity),
|
||||||
@@ -30,6 +30,7 @@ public class GetActivityEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetMilestoneEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/milestones/{milestoneId}", async (string milestoneId, ProjectService service,
|
builder.MapGet("api/projects/milestones/{milestoneId}", async (string milestoneId, IProjectService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetMilestoneAsync(milestoneId, cancellationToken) switch
|
await service.GetMilestoneAsync(milestoneId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,20 +14,20 @@ public class GetMilestonesEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/milestones/{page:int}/{pageSize:int}", async (ProjectService service,
|
builder.MapGet("api/projects/milestones/{page:int}/{pageSize:int}", async (IProjectService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetMilestonesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetMilestonesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.RequireAuthorization()
|
|
||||||
.WithDescription("Get a list of all milestones")
|
.WithDescription("Get a list of all milestones")
|
||||||
.WithName(typeof(GetMilestonesEndpoint).ToEndpointName())
|
.WithName(typeof(GetMilestonesEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces<Milestone[]>(StatusCodes.Status200OK)
|
.Produces<Milestone[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Projects;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetProgrammeEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/projects/programme/{programmeId}", async (string programmeId, IProjectService service,
|
||||||
|
CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetProgrammeAsync(programmeId, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var programme } => Results.Ok(programme),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Get specific programme")
|
||||||
|
.WithName(typeof(GetProgrammeEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Programme>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Projects;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetProgrammeProjectsEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/projects/{programmeId}/programme", async (string programmeId,
|
||||||
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetProgrammeProjectsAsync(programmeId, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var projects } => Results.Ok(projects),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems()),
|
||||||
|
})
|
||||||
|
.WithDescription("Get a list of all projects under a programme")
|
||||||
|
.WithName(typeof(GetProgrammeProjectsEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Project[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Projects;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class GetProgrammesEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/projects/programme/{page:int}/{pageSize:int}", async (IProjectService service,
|
||||||
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.GetProgrammesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var programmes } => Results.Ok(programmes),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Get a list of all programmes")
|
||||||
|
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName("Projects"))
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<Programme[]>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status404NotFound)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetProjectEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/{projectId}", async (string projectId, ProjectService service,
|
builder.MapGet("api/projects/{projectId}", async (string projectId, IProjectService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectAsync(projectId, cancellationToken) switch
|
await service.GetProjectAsync(projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -31,6 +31,7 @@ public class GetProjectEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetProjectIndicatorsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/indicators/project/{projectId}", async (string projectId, ProjectService service,
|
builder.MapGet("api/projects/indicators/project/{projectId}", async (string projectId, IProjectService service,
|
||||||
CancellationToken cancellationToken = default) =>
|
CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken) switch
|
await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -30,6 +30,7 @@ public class GetProjectIndicatorsEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -15,7 +15,7 @@ public class GetProjectMilestonesEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/{projectId}/milestones", async (string projectId,
|
builder.MapGet("api/projects/{projectId}/milestones", async (string projectId,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectMilestonesAsync(projectId, cancellationToken) switch
|
await service.GetProjectMilestonesAsync(projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
||||||
@@ -30,6 +30,7 @@ public class GetProjectMilestonesEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -15,7 +15,7 @@ public class GetProjectRisksEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects/{projectId}/risks", async (string projectId,
|
builder.MapGet("api/projects/{projectId}/risks", async (string projectId,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectRisksAsync(projectId, cancellationToken) switch
|
await service.GetProjectRisksAsync(projectId, cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true, Value: var risks } => Results.Ok(risks),
|
{ IsSuccess: true, Value: var risks } => Results.Ok(risks),
|
||||||
@@ -30,6 +30,7 @@ public class GetProjectRisksEndpoint : IEndpoint
|
|||||||
.Produces(StatusCodes.Status403Forbidden)
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -14,7 +14,7 @@ public class GetProjectsEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapGet("api/projects", async (ProjectService service,
|
builder.MapGet("api/projects", async (IProjectService service,
|
||||||
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
|
||||||
await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||||
{
|
{
|
||||||
@@ -27,6 +27,7 @@ public class GetProjectsEndpoint : IEndpoint
|
|||||||
.Produces<Project[]>(StatusCodes.Status200OK)
|
.Produces<Project[]>(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects)
|
||||||
|
.CacheOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Services.Projects;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
@@ -12,7 +13,7 @@ public sealed class UpdateMilestoneEndpoint : IEndpoint
|
|||||||
{
|
{
|
||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/projects/milestone", async (UpdateMilestoneRequest request, ProjectService service,
|
builder.MapPut("api/projects/milestone", async (UpdateMilestoneRequest request, IProjectService service,
|
||||||
HttpContext context, CancellationToken cancellationToken = default) =>
|
HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.UpdateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Projects;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Projects;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class UpdateProgrammeEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapPut("api/projects/programme", async (UpdateProgrammeRequest request, HttpContext context,
|
||||||
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
var r when r.HasError<NotFoundError>() => Results.NotFound(),
|
||||||
|
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Update existing programme")
|
||||||
|
.WithName(typeof(UpdateProgrammeEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Projects);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Services.Projects;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
@@ -14,20 +15,20 @@ public sealed class UpdateProjectEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/projects", async (UpdateProjectRequest request,
|
builder.MapPut("api/projects", async (UpdateProjectRequest request,
|
||||||
ProjectService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
IProjectService service, HttpContext context, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateProjectAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
await service.UpdateProjectAsync(request, userId: context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
})
|
})
|
||||||
.WithDescription("Update project details")
|
.WithDescription("Update project details")
|
||||||
.WithName(typeof(UpdateProjectEndpoint).ToEndpointName())
|
.WithName(typeof(UpdateProjectEndpoint).ToEndpointName())
|
||||||
.MapToApiVersion(new ApiVersion(1))
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
.Produces(StatusCodes.Status200OK)
|
.Produces(StatusCodes.Status200OK)
|
||||||
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
.Produces(StatusCodes.Status401Unauthorized)
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
.WithTags(EndpointTags.Projects);
|
.WithTags(EndpointTags.Projects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Application.Extensions;
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Projects;
|
using PostFundManagement.Application.Services.Projects;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
@@ -14,7 +15,7 @@ public class UpdateProjectStatusEndpoint : IEndpoint
|
|||||||
public void Map(IEndpointRouteBuilder builder)
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
{
|
{
|
||||||
builder.MapPut("api/projects/status", async (UpdateProjectStatusRequest request, HttpContext context,
|
builder.MapPut("api/projects/status", async (UpdateProjectStatusRequest request, HttpContext context,
|
||||||
ProjectService service, CancellationToken cancellationToken = default) =>
|
IProjectService service, CancellationToken cancellationToken = default) =>
|
||||||
await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch
|
await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch
|
||||||
{
|
{
|
||||||
{ IsSuccess: true } => Results.Ok(),
|
{ IsSuccess: true } => Results.Ok(),
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Domain.Extensions;
|
||||||
|
using PostFundManagement.Domain.Security;
|
||||||
|
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Api.Endpoints.Tools;
|
||||||
|
|
||||||
|
[ApiVersionTarget(1)]
|
||||||
|
public sealed class EncodeIdEndpoint : IEndpoint
|
||||||
|
{
|
||||||
|
public void Map(IEndpointRouteBuilder builder)
|
||||||
|
{
|
||||||
|
builder.MapGet("api/tools/hashid/{id64:long}", async (long id64, IToolService service,
|
||||||
|
CancellationToken cancellationToken) =>
|
||||||
|
await service.EncodeLong(id64, cancellationToken) switch
|
||||||
|
{
|
||||||
|
{ IsSuccess: true, Value: var hash } => Results.Ok(hash),
|
||||||
|
var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
|
||||||
|
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||||
|
})
|
||||||
|
.WithDescription("Hashes an int64 identifier")
|
||||||
|
.WithName(typeof(EncodeIdEndpoint).ToEndpointName())
|
||||||
|
.MapToApiVersion(new ApiVersion(1))
|
||||||
|
.Produces<string>(StatusCodes.Status200OK)
|
||||||
|
.Produces(StatusCodes.Status401Unauthorized)
|
||||||
|
.Produces(StatusCodes.Status403Forbidden)
|
||||||
|
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||||
|
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||||
|
.WithTags(EndpointTags.Tools)
|
||||||
|
.CacheOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,14 @@
|
|||||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Security (IODC)-->
|
<!-- Security (IODC)-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ builder.Services.AddHealthChecks()
|
|||||||
|
|
||||||
IEnumerable<string> identityScopes = ["openid", "profile", "roles", "email", "address"];
|
IEnumerable<string> identityScopes = ["openid", "profile", "roles", "email", "address"];
|
||||||
|
|
||||||
|
builder.Services.AddValidation()
|
||||||
|
.AddEndpointValidation();
|
||||||
|
|
||||||
builder.Services.AddOpenApi(options =>
|
builder.Services.AddOpenApi(options =>
|
||||||
{
|
{
|
||||||
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
options.AddDocumentTransformer((document, context, cancellationToken) =>
|
||||||
@@ -88,6 +91,12 @@ builder.Services.AddOpenApi(options =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddCors(options => options.AddPolicy("AllowFrontend", policy =>
|
||||||
|
policy.WithOrigins("https://localhost:3000", "https://localhost:8076", "https://console.ifri.khongisa.co.za")
|
||||||
|
.AllowAnyHeader()
|
||||||
|
.AllowAnyMethod()
|
||||||
|
.AllowCredentials()));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
||||||
@@ -100,6 +109,7 @@ app.UseHsts();
|
|||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
app.UseCors("AllowFrontend");
|
||||||
app.UseOutputCache();
|
app.UseOutputCache();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
@@ -111,7 +121,7 @@ ApiVersionSet versionSet = app.NewApiVersionSet("v1")
|
|||||||
|
|
||||||
var versionGroups = new Dictionary<int, RouteGroupBuilder>
|
var versionGroups = new Dictionary<int, RouteGroupBuilder>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet)
|
1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet)
|
||||||
.RequireAuthorization("ApiScope").AddEndpointFilter<ActiveUserFilter>()
|
.RequireAuthorization("ApiScope").AddEndpointFilter<ActiveUserFilter>()
|
||||||
}
|
}
|
||||||
@@ -140,7 +150,7 @@ foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.A
|
|||||||
app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) =>
|
app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) =>
|
||||||
{
|
{
|
||||||
options.AddServer(new ScalarServer($"https://{context.Request.Host}"));
|
options.AddServer(new ScalarServer($"https://{context.Request.Host}"));
|
||||||
options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json");
|
options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json");
|
||||||
options.WithTheme(ScalarTheme.DeepSpace);
|
options.WithTheme(ScalarTheme.DeepSpace);
|
||||||
options.Agent = new ScalarAgentOptions { Disabled = true };
|
options.Agent = new ScalarAgentOptions { Disabled = true };
|
||||||
options.AddPreferredSecuritySchemes("OAuth2")
|
options.AddPreferredSecuritySchemes("OAuth2")
|
||||||
|
|||||||
@@ -10,5 +10,7 @@ public interface ICommunicationsService
|
|||||||
|
|
||||||
ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid userId, CancellationToken cancellationToken = default);
|
ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using PostFundManagement.Application.Services.Execution;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IExecutionService
|
||||||
|
{
|
||||||
|
ValueTask<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using PostFundManagement.Application.Services.Governance;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IGovernanceService
|
||||||
|
{
|
||||||
|
ValueTask<Result<Programme[]>> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Portfolio[]>> GetPorfoliosAsync(string organisationIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Organisation[]>> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Portfolio>> GetPortfolioAsync(string portfolioIdHash, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using PostFundManagement.Application.Services.Grants;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IGrantsService
|
||||||
|
{
|
||||||
|
ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Contract>> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Budget>> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using PostFundManagement.Application.Services.Projects;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IProjectService
|
||||||
|
{
|
||||||
|
ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Project>> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Indicator[]>> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
ValueTask<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace PostFundManagement.Application.Abstractions.Services;
|
||||||
|
|
||||||
|
public interface IToolService
|
||||||
|
{
|
||||||
|
ValueTask<Result<string>> EncodeLong(long id, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@@ -6,6 +6,8 @@ namespace PostFundManagement.Application.Extensions;
|
|||||||
|
|
||||||
public static class Api
|
public static class Api
|
||||||
{
|
{
|
||||||
|
public static IServiceCollection AddEndpointValidation(this IServiceCollection services) => services.AddValidation();
|
||||||
|
|
||||||
public static IServiceCollection AddEndpointAndServiceCaching(this IServiceCollection services, IConfiguration configuration)
|
public static IServiceCollection AddEndpointAndServiceCaching(this IServiceCollection services, IConfiguration configuration)
|
||||||
{
|
{
|
||||||
services.AddStackExchangeRedisOutputCache(options =>
|
services.AddStackExchangeRedisOutputCache(options =>
|
||||||
|
|||||||
@@ -1,55 +1,12 @@
|
|||||||
using PostFundManagement.Application.Abstractions;
|
namespace PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Abstractions.Services;
|
|
||||||
using PostFundManagement.Application.Services.Communications;
|
|
||||||
using PostFundManagement.Application.Services.Evidence;
|
|
||||||
using PostFundManagement.Application.Services.Shared;
|
|
||||||
using PostFundManagement.Application.Services.Users;
|
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Extensions;
|
|
||||||
|
|
||||||
public static class General
|
public static class General
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration)
|
public static string Clean(this string input) => Patterns.SpaceRegex().Replace(input, string.Empty);
|
||||||
{
|
|
||||||
services.AddScoped<ICommunicationsService, CommunicationsService>();
|
|
||||||
services.Decorate<ICommunicationsService, CommunicationsCacheService>();
|
|
||||||
|
|
||||||
services.AddScoped<IEvidenceService, EvidenceService>();
|
public static string ToJson<TValue>(this TValue obj) where TValue : class => JsonSerializer.Serialize(obj);
|
||||||
services.Decorate<IEvidenceService, EvidenceCacheService>();
|
|
||||||
|
|
||||||
services.AddScoped<IUserService, UserService>();
|
public static TValue FromJson<TValue>(this string json) where TValue : class => JsonSerializer.Deserialize<TValue>(json)!;
|
||||||
services.Decorate<IUserService, UserCacheService>();
|
|
||||||
|
|
||||||
services.AddScoped<IIdentityService, IdentityService>();
|
|
||||||
services.Decorate<IIdentityService, IdentityCacheService>();
|
|
||||||
|
|
||||||
services.AddStackExchangeRedisCache(options =>
|
|
||||||
{
|
|
||||||
options.Configuration = configuration.GetConnectionString("Redis");
|
|
||||||
options.InstanceName = "PFM-API_";
|
|
||||||
});
|
|
||||||
|
|
||||||
services.AddHybridCache(options => options.DefaultEntryOptions = new HybridCacheEntryOptions
|
|
||||||
{
|
|
||||||
Expiration = TimeSpan.FromMinutes(2),
|
|
||||||
LocalCacheExpiration = TimeSpan.FromMinutes(30)
|
|
||||||
});
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IServiceCollection AddSharedServices(this IServiceCollection services)
|
|
||||||
{
|
|
||||||
var serviceType = typeof(IService);
|
|
||||||
|
|
||||||
var sharedImplementations = typeof(IApplication).Assembly.GetTypes()
|
|
||||||
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
|
||||||
|
|
||||||
foreach (var sharedImplementation in sharedImplementations) services.AddScoped(sharedImplementation);
|
|
||||||
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Guid GetUserId(this HttpContext context) => (Guid)context.Items[Domain.Extensions.Constants.UserIdItemKey]!;
|
public static Guid GetUserId(this HttpContext context) => (Guid)context.Items[Domain.Extensions.Constants.UserIdItemKey]!;
|
||||||
|
|
||||||
@@ -77,6 +34,6 @@ public static class General
|
|||||||
{
|
{
|
||||||
Detail = string.Join(',', errors.Select(e => e.Message)),
|
Detail = string.Join(',', errors.Select(e => e.Message)),
|
||||||
Status = StatusCodes.Status400BadRequest,
|
Status = StatusCodes.Status400BadRequest,
|
||||||
Title = errors[0]?.Message,
|
Title = "Error",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace PostFundManagement.Application.Extensions;
|
||||||
|
|
||||||
|
public partial class Patterns
|
||||||
|
{
|
||||||
|
[GeneratedRegex(@"\s+")]
|
||||||
|
public static partial Regex SpaceRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\A\b[0-9a-fA-F]+\b\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
||||||
|
public static partial Regex HexHashRegex { get; }
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\A[0-9a-fA-F]{32}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
||||||
|
public static partial Regex Md5Regex { get; }
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\A[0-9a-fA-F]{64}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
||||||
|
public static partial Regex Sha256Regex { get; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Communications;
|
||||||
|
using PostFundManagement.Application.Services.Evidence;
|
||||||
|
using PostFundManagement.Application.Services.Execution;
|
||||||
|
using PostFundManagement.Application.Services.Governance;
|
||||||
|
using PostFundManagement.Application.Services.Grants;
|
||||||
|
using PostFundManagement.Application.Services.Projects;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
using PostFundManagement.Application.Services.Tools;
|
||||||
|
using PostFundManagement.Application.Services.Users;
|
||||||
|
using PostFundManagement.Domain.Abstractions;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Extensions;
|
||||||
|
|
||||||
|
public static class Services
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddScoped<ICommunicationsService, CommunicationsService>();
|
||||||
|
services.Decorate<ICommunicationsService, CommunicationsCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IEvidenceService, EvidenceService>();
|
||||||
|
services.Decorate<IEvidenceService, EvidenceCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IUserService, UserService>();
|
||||||
|
services.Decorate<IUserService, UserCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IIdentityService, IdentityService>();
|
||||||
|
services.Decorate<IIdentityService, IdentityCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IExecutionService, ExecutionService>();
|
||||||
|
services.Decorate<IExecutionService, ExecutionCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IGovernanceService, GovernanceService>();
|
||||||
|
services.Decorate<IGovernanceService, GovernanceCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IGrantsService, GrantsService>();
|
||||||
|
services.Decorate<IGrantsService, GrantsCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IProjectService, ProjectService>();
|
||||||
|
services.Decorate<IProjectService, ProjectCacheService>();
|
||||||
|
|
||||||
|
services.AddScoped<IToolService, ToolService>();
|
||||||
|
services.Decorate<IToolService, ToolCacheService>();
|
||||||
|
|
||||||
|
services.AddStackExchangeRedisCache(options =>
|
||||||
|
{
|
||||||
|
options.Configuration = configuration.GetConnectionString("Redis");
|
||||||
|
options.InstanceName = "PFM-API_";
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddHybridCache(options => options.DefaultEntryOptions = new HybridCacheEntryOptions
|
||||||
|
{
|
||||||
|
Expiration = TimeSpan.FromMinutes(2),
|
||||||
|
LocalCacheExpiration = TimeSpan.FromMinutes(30)
|
||||||
|
});
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IServiceCollection AddSharedServices(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
var serviceType = typeof(IService);
|
||||||
|
|
||||||
|
var sharedImplementations = typeof(IApplication).Assembly.GetTypes()
|
||||||
|
.Where(t => serviceType.IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
||||||
|
|
||||||
|
foreach (var sharedImplementation in sharedImplementations) services.AddScoped(sharedImplementation);
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,14 @@
|
|||||||
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
<PackageLicenseFile>..\LICENSE</PackageLicenseFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\PostFundManagement.Domain\PostFundManagement.Domain.csproj" />
|
<ProjectReference Include="..\PostFundManagement.Domain\PostFundManagement.Domain.csproj" />
|
||||||
<ProjectReference Include="..\PostFundManagement.Infrastructure\PostFundManagement.Infrastructure.csproj" />
|
<ProjectReference Include="..\PostFundManagement.Infrastructure\PostFundManagement.Infrastructure.csproj" />
|
||||||
@@ -17,6 +25,7 @@
|
|||||||
<!-- API Documentation -->
|
<!-- API Documentation -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Validation" Version="10.0.11" />
|
||||||
<PackageReference Include="Scalar.AspNetCore" Version="2.17.2" />
|
<PackageReference Include="Scalar.AspNetCore" Version="2.17.2" />
|
||||||
|
|
||||||
<Using Include="Scalar.AspNetCore" />
|
<Using Include="Scalar.AspNetCore" />
|
||||||
@@ -26,6 +35,8 @@
|
|||||||
|
|
||||||
<!-- Shared Usings -->
|
<!-- Shared Usings -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Using Include="Humanizer" />
|
||||||
|
<Using Include="System.ComponentModel.DataAnnotations" />
|
||||||
<Using Include="Microsoft.Extensions.Caching.Distributed" />
|
<Using Include="Microsoft.Extensions.Caching.Distributed" />
|
||||||
<Using Include="Microsoft.Extensions.Caching.Hybrid" />
|
<Using Include="Microsoft.Extensions.Caching.Hybrid" />
|
||||||
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||||
|
|||||||
+58
-6
@@ -1,22 +1,74 @@
|
|||||||
using PostFundManagement.Application.Abstractions;
|
using PostFundManagement.Application.Abstractions;
|
||||||
using PostFundManagement.Application.Abstractions.Services;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Communications;
|
namespace PostFundManagement.Application.Services.Communications;
|
||||||
|
|
||||||
public sealed class CommunicationsCacheService(CommunicationsService service, HybridCache cache) : ServiceCacheBase, ICommunicationsService
|
public sealed class CommunicationsCacheService(CommunicationsService service, IDistributedCache cache) : ServiceCacheBase, ICommunicationsService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Invite>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Invite>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getapplicantinvite-{userId}").Value,
|
{
|
||||||
async cancel => await service.GetApplicantInviteAsync(userId, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getapplicantinvite-{applicantUserId}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetApplicantInviteAsync(applicantUserId, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached!.FromJson<Invite>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Invite[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getapplicantinvites-{applicantUserId}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetApplicantInvitesAsync(applicantUserId, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Invite[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Invite[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Invite[]>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getinvites-{pagination.Page}-{pagination.PageSize}").Value,
|
{
|
||||||
async cancel => await service.GetInvitesAsync(pagination, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getinvites-{pagination.Page}-{pagination.PageSize}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetInvitesAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(result.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Invite[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await service.SendInviteAsync(request, userId, cancellationToken);
|
await service.SendInviteAsync(request, userId, cancellationToken);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using PostFundManagement.Application.Abstractions.Services;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
@@ -39,9 +38,8 @@ public sealed class CommunicationsService(IDbContextFactory<ApplicationDbContext
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New)
|
||||||
? Result.Ok(invite.Map())
|
.ToResult(invite.Map());
|
||||||
: Result.Fail<Invite>(CreateError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -58,15 +56,27 @@ public sealed class CommunicationsService(IDbContextFactory<ApplicationDbContext
|
|||||||
return Result.Ok(invites.Select(i => i.Map()).ToArray());
|
return Result.Ok(invites.Select(i => i.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid userId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Invite>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Invite>>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var invite = await context.Invites.AsNoTracking().FirstOrDefaultAsync(i => i.Recipient == userId, cancellationToken);
|
var invite = await context.Invites.AsNoTracking().FirstOrDefaultAsync(i => i.Recipient == applicantUserId, cancellationToken);
|
||||||
|
|
||||||
return invite is not null
|
return Result.FailIf(invite is null, NotFoundError.New)
|
||||||
? Result.Ok(invite.Map())
|
.ToResult(invite!.Map());
|
||||||
: Result.Fail<Invite>(NotFoundError.New);
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Invite[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var invites = await context.Invites.AsNoTracking()
|
||||||
|
.Where(i => i.Recipient == applicantUserId)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(invites.Select(i => i.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Communications;
|
namespace PostFundManagement.Application.Services.Communications;
|
||||||
|
|
||||||
public record InviteRequest(long OrganisationId, long InvitedOrganisationId, long AwardId, long ContractId, Guid Recipient, string Message, NotificationPlatform Platform);
|
public record InviteRequest([Required] long OrganisationId,
|
||||||
|
[Required] long InvitedOrganisationId,
|
||||||
|
[Required] long AwardId,
|
||||||
|
[Required] long ContractId,
|
||||||
|
[Required] Guid Recipient,
|
||||||
|
[Required][MaxLength(LabelLength)] string Message,
|
||||||
|
[Required] NotificationPlatform Platform);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
using PostFundManagement.Application.Abstractions;
|
using PostFundManagement.Application.Abstractions;
|
||||||
using PostFundManagement.Application.Abstractions.Services;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Evidence;
|
namespace PostFundManagement.Application.Services.Evidence;
|
||||||
|
|
||||||
public sealed class EvidenceCacheService(EvidenceService service, HybridCache cache) : ServiceCacheBase, IEvidenceService
|
public sealed class EvidenceCacheService(EvidenceService service, IDistributedCache cache) : ServiceCacheBase, IEvidenceService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default) =>
|
||||||
await service.CreateEvidenceAsync(userId, form, file, cancellationToken);
|
await service.CreateEvidenceAsync(userId, form, file, cancellationToken);
|
||||||
@@ -19,33 +20,129 @@ public sealed class EvidenceCacheService(EvidenceService service, HybridCache ca
|
|||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getevidence-{evidenceId}").Value,
|
{
|
||||||
async cancel => await service.GetEvidenceAsync(evidenceId, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getevidence-{evidenceId}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetEvidenceAsync(evidenceId, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Evidence>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getevidencefiles-{pagination.Page}-{pagination.PageSize}").Value,
|
{
|
||||||
async cancel => await service.GetEvidenceFilesAsync(pagination, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getevidencefiles-{pagination.Page}-{pagination.PageSize}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetEvidenceFilesAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Evidence[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Indicator>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Indicator>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getindicator-{indicatorId}").Value,
|
{
|
||||||
async cancel => await service.GetIndicatorAsync(indicatorId, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getindicator-{indicatorId}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetIndicatorAsync(indicatorId, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Indicator>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Indicator[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Indicator[]>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getindicators-{pagination.Page}-{pagination.PageSize}").Value,
|
{
|
||||||
async cancel => await service.GetIndicatorsAsync(pagination, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getindicators-{pagination.Page}-{pagination.PageSize}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetIndicatorsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Indicator[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getmilestoneevidences-{milestoneIdHash}").Value,
|
{
|
||||||
async cancel => await service.GetMilestoneEvidencesAsync(milestoneIdHash, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getmilestoneevidences-{milestoneIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetMilestoneEvidencesAsync(milestoneIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Evidence[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Evidence[]>>>)(async () =>
|
||||||
await cache.GetOrCreateAsync(HashService.ToMd5Hash($"getprojectevidences-{projectIdHash}").Value,
|
{
|
||||||
async cancel => await service.GetProjectEvidencesAsync(projectIdHash, cancellationToken))));
|
var cacheKey = HashService.ToMd5Hash($"getprojectevidences-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectEvidencesAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Evidence[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await service.RecordIndicatorActualAmountAsync(indicatorIdHash, amount, userId, cancellationToken);
|
await service.RecordIndicatorActualAmountAsync(indicatorIdHash, amount, userId, cancellationToken);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
|
|
||||||
context.Evidences.Add(evidence);
|
context.Evidences.Add(evidence);
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -66,7 +66,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
.SetProperty(f => f.ActualAmount, amount)
|
.SetProperty(f => f.ActualAmount, amount)
|
||||||
.SetProperty(f => f.CreatedBy, userId), cancellationToken);
|
.SetProperty(f => f.CreatedBy, userId), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -102,6 +102,9 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
|
||||||
return Result.Fail(NotFoundError.New);
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
@@ -116,7 +119,8 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0,
|
||||||
|
CreateError.Create("No record could be created."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -139,7 +143,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken) =>
|
||||||
@@ -155,9 +159,8 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
|
|
||||||
var indicator = await context.Indicators.AsNoTracking().FirstOrDefaultAsync(i => i.Id == decodeResult.Value, cancellationToken);
|
var indicator = await context.Indicators.AsNoTracking().FirstOrDefaultAsync(i => i.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return indicator is not null
|
return Result.FailIf(indicator is null, NotFoundError.New)
|
||||||
? Result.Ok(indicator.Map())
|
.ToResult(indicator!.Map());
|
||||||
: Result.Fail(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -173,9 +176,8 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
|
|
||||||
var evidence = await context.Evidences.AsNoTracking().FirstOrDefaultAsync(e => e.Id == decodeResult.Value, cancellationToken);
|
var evidence = await context.Evidences.AsNoTracking().FirstOrDefaultAsync(e => e.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return evidence is not null
|
return Result.FailIf(evidence is null, NotFoundError.New)
|
||||||
? Result.Ok(evidence.Map())
|
.ToResult(evidence!.Map());
|
||||||
: Result.Fail<Domain.Models.Evidence>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
||||||
@@ -234,6 +236,6 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
.SetProperty(f => f.UpdatedBy, userId)
|
.SetProperty(f => f.UpdatedBy, userId)
|
||||||
.SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
.SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Evidence;
|
namespace PostFundManagement.Application.Services.Evidence;
|
||||||
|
|
||||||
public record UpdateEvidenceRequest(long EvidenceId, int Version, EvidenceType EvidenceType, ApprovalStatus Status);
|
public record UpdateEvidenceRequest([Required] long EvidenceId,
|
||||||
|
[Required] int Version,
|
||||||
|
[Required] EvidenceType EvidenceType,
|
||||||
|
[Required] ApprovalStatus Status);
|
||||||
|
|
||||||
public record RecordActualRequest(decimal ActualAmount);
|
public record RecordActualRequest([Required] decimal ActualAmount);
|
||||||
|
|
||||||
public record CreateMilestoneRequest(long ProjectId, DateTime DueAt, string Name, Priority Priority);
|
public record CreateMilestoneRequest([Required] long ProjectId,
|
||||||
|
[Required] DateTime DueAt,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required] Priority Priority);
|
||||||
|
|
||||||
public record CreateIndicatorRequest(long ProjectId, string Name, UnitOfMeasure UnitOfMeasure, decimal BaselineAmount, decimal TargetAmount);
|
public record CreateIndicatorRequest([Required] long ProjectId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required] UnitOfMeasure UnitOfMeasure,
|
||||||
|
[Required] decimal BaselineAmount,
|
||||||
|
[Required] decimal TargetAmount);
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Execution;
|
||||||
|
|
||||||
|
public sealed class ExecutionCacheService(ExecutionService service, IDistributedCache cache) : ServiceCacheBase, IExecutionService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.ApproveDisbursementAsync(disbursementIdHash, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateDisbursementAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateRiskAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Disbursement>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getdisbursement-{disbursementIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetDisbursementAsync(disbursementIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Disbursement>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Disbursement[]>> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Disbursement[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getdisbursements-{pagination.Page}-{pagination.PageSize}-{pagination.Index}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetDisbursementsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Disbursement[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Risk>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getrisk-{riskIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetRiskAsync(riskIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Risk>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Risk[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getrisks-{pagination.Page}-{pagination.PageSize}-{pagination.Index}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetRisksAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Risk[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateDisbursementAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateRiskAsync(request, cancellationToken);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
@@ -8,7 +8,7 @@ using PostFundManagement.Infrastructure.Database;
|
|||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Execution;
|
namespace PostFundManagement.Application.Services.Execution;
|
||||||
|
|
||||||
public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IService
|
public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IExecutionService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Risk[]>> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Risk[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Risk[]>>>)(async () =>
|
||||||
@@ -57,7 +57,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -78,7 +78,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -98,7 +98,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
.SetProperty(f => f.UpdatedBy, userId)
|
.SetProperty(f => f.UpdatedBy, userId)
|
||||||
.SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
.SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Disbursement>> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken) =>
|
||||||
@@ -115,12 +115,11 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
var disbursement = await context.Disbursements.AsNoTracking()
|
var disbursement = await context.Disbursements.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(d => d.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(d => d.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return disbursement is not null
|
return Result.FailIf(disbursement is null, NotFoundError.New)
|
||||||
? Result.Ok(disbursement.Map())
|
.ToResult(disbursement!.Map());
|
||||||
: Result.Fail<Disbursement>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Risk>> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Risk>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Risk>>>)(async () =>
|
||||||
{
|
{
|
||||||
var decodeResult = hashService.DecodeLongIdHash(riskIdHash);
|
var decodeResult = hashService.DecodeLongIdHash(riskIdHash);
|
||||||
@@ -134,12 +133,11 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
var risk = await context.Risks.AsNoTracking()
|
var risk = await context.Risks.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(r => r.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(r => r.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return risk is not null
|
return Result.FailIf(risk is null, NotFoundError.New)
|
||||||
? Result.Ok(risk.Map())
|
.ToResult(risk!.Map());
|
||||||
: Result.Fail<Risk>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken) =>
|
public async ValueTask<Result> UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
@@ -152,10 +150,10 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
.SetProperty(u => u.Likelihood, request.Likelihood)
|
.SetProperty(u => u.Likelihood, request.Likelihood)
|
||||||
.SetProperty(u => u.Impact, request.Impact), cancellationToken);
|
.SetProperty(u => u.Impact, request.Impact), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result> UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
@@ -168,6 +166,6 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
|||||||
.SetProperty(d => d.UpdatedAt, DateTime.UtcNow)
|
.SetProperty(d => d.UpdatedAt, DateTime.UtcNow)
|
||||||
.SetProperty(d => d.UpdatedBy, userId), cancellationToken);
|
.SetProperty(d => d.UpdatedBy, userId), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Execution;
|
namespace PostFundManagement.Application.Services.Execution;
|
||||||
|
|
||||||
public record UpdateDisbursementRequest(long DisbursementId, long? MilestoneId, decimal Amount);
|
public record UpdateDisbursementRequest([Required] long DisbursementId, long? MilestoneId, [Required] decimal Amount);
|
||||||
|
|
||||||
public record UpdateRiskRequest(long RiskId, RiskLikelihood Likelihood, RiskImpact Impact, string Name, string Description);
|
public record UpdateRiskRequest([Required] long RiskId,
|
||||||
|
[Required] RiskLikelihood Likelihood,
|
||||||
|
[Required] RiskImpact Impact,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description);
|
||||||
|
|
||||||
public record CreateDisbursementRequest(long ProjectId, long? MilestoneId, decimal Amount);
|
public record CreateDisbursementRequest([Required] long ProjectId, long? MilestoneId, [Required] decimal Amount);
|
||||||
|
|
||||||
public record CreateRiskRequest(long ProjectId, RiskLikelihood Likelihood, RiskImpact Impact, string Name, string Description);
|
public record CreateRiskRequest([Required] long ProjectId,
|
||||||
|
[Required] RiskLikelihood Likelihood,
|
||||||
|
[Required] RiskImpact Impact,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description);
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Governance;
|
||||||
|
|
||||||
|
public sealed class GovernanceCacheService(GovernanceService service, IDistributedCache cache) : ServiceCacheBase, IGovernanceService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateOrganisationAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreatePortfolioAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result<Dashboard>> GetDashboardAsync(CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Dashboard>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getdashboard").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetDashboardAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Dashboard>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Instrument[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getinstruments").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetInstrumentsAsync(programmeIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Instrument[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Organisation>> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Organisation>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getorganisation-{organisationIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetOrganisationAsync(organisationIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Organisation>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Organisation[]>> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Organisation[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getorganisations-{pagination.Page}-{pagination.PageSize}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetOrganisationsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Organisation[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Portfolio[]>> GetPorfoliosAsync(string organisationIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Portfolio[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getportfolios-{organisationIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetPorfoliosAsync(organisationIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Portfolio[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Portfolio>> GetPortfolioAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Portfolio>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getportfolio-{portfolioIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetPortfolioAsync(portfolioIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Portfolio>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getportfolioprogrammes-{portfolioIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetPortfolioProgrammesAsync(portfolioIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Programme[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme[]>> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprogrammes-{portfolioIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProgrammesAsync(portfolioIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Programme[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateOrganisationAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdatePortfolioAsync(request, userId, cancellationToken);
|
||||||
|
}
|
||||||
@@ -1,31 +1,50 @@
|
|||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
using PostFundManagement.Infrastructure.Database;
|
using PostFundManagement.Infrastructure.Database;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Governance;
|
namespace PostFundManagement.Application.Services.Governance;
|
||||||
|
|
||||||
public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IService
|
public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IGovernanceService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<Programme[]>> GetProgrammesAsync(CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Programme[]>> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
{
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(portfolioIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed)
|
||||||
|
return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var programmes = await context.Programmes.AsNoTracking().ToListAsync(cancellationToken);
|
var programmes = await context.Programmes.AsNoTracking()
|
||||||
|
.Where(p => p.PortfolioId == decodeResult.Value)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(programmes.Select(p => p.Map()).ToArray());
|
return Result.Ok(programmes.Select(p => p.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Portfolio[]>> GetPorfoliosAsync(CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Portfolio[]>> GetPorfoliosAsync(string organisationIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Portfolio[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Portfolio[]>>>)(async () =>
|
||||||
{
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(organisationIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed)
|
||||||
|
return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var portfolios = await context.Portfolios.AsNoTracking().ToListAsync(cancellationToken);
|
var portfolios = await context.Portfolios.AsNoTracking()
|
||||||
|
.Where(f => f.Id == decodeResult.Value)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(portfolios.Select(p => p.Map()).ToArray());
|
return Result.Ok(portfolios.Select(p => p.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
@@ -58,17 +77,25 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
|||||||
var organisation = await context.Organisations.AsNoTracking()
|
var organisation = await context.Organisations.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return organisation is not null
|
return Result.FailIf(organisation is null, NotFoundError.New)
|
||||||
? Result.Ok(organisation.Map())
|
.ToResult(organisation!.Map());
|
||||||
: Result.Fail(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Instrument[]>> GetInstrumentsAsync(CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Instrument[]>> GetInstrumentsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Instrument[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Instrument[]>>>)(async () =>
|
||||||
{
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(programmeIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed)
|
||||||
|
return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var instruments = await context.Instruments.AsNoTracking().ToListAsync(cancellationToken);
|
var instruments = await context.Instruments.AsNoTracking()
|
||||||
|
.Where(i => i.ProgrammeId == decodeResult.Value)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(instruments.Select(i => i.Map()).ToArray());
|
return Result.Ok(instruments.Select(i => i.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
@@ -104,21 +131,23 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
|||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (await context.Organisations.AnyAsync(o => o.RegistrationNo == request.RegistrationNo, cancellationToken))
|
var registrationNo = request.RegistrationNo.Clean();
|
||||||
|
|
||||||
|
if (await context.Organisations.AnyAsync(o => o.RegistrationNo == registrationNo, cancellationToken))
|
||||||
return Result.Fail(CreateError.Create("Organisation with the same registration number already exists"));
|
return Result.Fail(CreateError.Create("Organisation with the same registration number already exists"));
|
||||||
|
|
||||||
context.Organisations.Add(new Domain.Entities.Organisation
|
context.Organisations.Add(new Domain.Entities.Organisation
|
||||||
{
|
{
|
||||||
Name = request.Name,
|
Name = request.Name.Humanize(LetterCasing.Title).Trim(),
|
||||||
RegistrationNo = request.RegistrationNo,
|
RegistrationNo = registrationNo,
|
||||||
Email = request.Email,
|
Email = request.Email.ToLower(CultureInfo.InvariantCulture).Clean(),
|
||||||
Type = request.Type,
|
Type = request.Type,
|
||||||
Status = ActivityStatus.Active,
|
Status = ActivityStatus.Active,
|
||||||
CreatedBy = userId,
|
CreatedBy = userId,
|
||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Organisation>> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) =>
|
||||||
@@ -129,11 +158,95 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
|||||||
var changedTotal = await context.Organisations
|
var changedTotal = await context.Organisations
|
||||||
.Where(f => f.Id == request.OrganisationId)
|
.Where(f => f.Id == request.OrganisationId)
|
||||||
.ExecuteUpdateAsync(f => f
|
.ExecuteUpdateAsync(f => f
|
||||||
.SetProperty(u => u.Name, request.Name)
|
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||||
.SetProperty(u => u.RegistrationNo, request.RegistrationNo)
|
.SetProperty(u => u.RegistrationNo, request.RegistrationNo.Clean())
|
||||||
.SetProperty(u => u.Email, request.Email)
|
.SetProperty(u => u.Email, request.Email.ToLower(CultureInfo.InvariantCulture).Clean())
|
||||||
.SetProperty(u => u.Type, request.Type), cancellationToken);
|
.SetProperty(u => u.Type, request.Type), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreatePortfolioAsync(CreatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Users.AnyAsync(u => u.Id == request.OwnerId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.Create("Owner user not found"));
|
||||||
|
|
||||||
|
if (!await context.Organisations.AnyAsync(u => u.Id == request.OrganisationId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.Create("Organisation not found"));
|
||||||
|
|
||||||
|
var name = request.Name.Humanize(LetterCasing.Title).Trim();
|
||||||
|
|
||||||
|
if (await context.Portfolios.AnyAsync(p => p.Name == name && p.OrganisationId == request.OrganisationId, cancellationToken))
|
||||||
|
return Result.Fail(ValidationError.Create("A portfolio with the same name exists in this organisation"));
|
||||||
|
|
||||||
|
context.Portfolios.Add(new Domain.Entities.Portfolio
|
||||||
|
{
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
CreatedBy = userId,
|
||||||
|
OrganisationId = request.OrganisationId,
|
||||||
|
OwnedBy = request.OwnerId,
|
||||||
|
Name = name,
|
||||||
|
Description = request.Description.Humanize(LetterCasing.Sentence).Trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdatePortfolioAsync(UpdatePortfolioRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Users.AnyAsync(u => u.Id == request.OwnerId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.Create("Owner user not found"));
|
||||||
|
|
||||||
|
var changedTotal = await context.Portfolios
|
||||||
|
.Where(p => p.Id == request.PortfolioId)
|
||||||
|
.ExecuteUpdateAsync(f => f
|
||||||
|
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||||
|
.SetProperty(u => u.OwnedBy, request.OwnerId)
|
||||||
|
.SetProperty(u => u.Description, request.Description.Humanize(LetterCasing.Sentence).Trim()), cancellationToken);
|
||||||
|
|
||||||
|
return Result.OkIf(changedTotal > 0, UpdateError.Create("Nothing was saved, no change detected."));
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme[]>> GetPortfolioProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(portfolioIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed)
|
||||||
|
return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var programmes = await context.Programmes.AsNoTracking()
|
||||||
|
.Where(f => f.PortfolioId == decodeResult.Value)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(programmes.Select(p => p.Map()).ToArray());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Portfolio>> GetPortfolioAsync(string portfolioIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Portfolio>>>)(async () =>
|
||||||
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(portfolioIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed)
|
||||||
|
return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var portfolio = await context.Portfolios.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
|
return Result.FailIf(portfolio is null, NotFoundError.New)
|
||||||
|
.ToResult(portfolio!.Map());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Governance;
|
namespace PostFundManagement.Application.Services.Governance;
|
||||||
|
|
||||||
public record UpdateOrganisationRequest(long OrganisationId, string Name, string RegistrationNo, string Email, OrganisationType Type);
|
public record UpdatePortfolioRequest([Required] long PortfolioId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description,
|
||||||
|
[Required] Guid OwnerId);
|
||||||
|
|
||||||
public record CreateOrganisationRequest(string Name, string RegistrationNo, string Email, OrganisationType Type);
|
public record CreatePortfolioRequest([Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description,
|
||||||
|
[Required] long OrganisationId,
|
||||||
|
[Required] Guid OwnerId);
|
||||||
|
|
||||||
public record Dashboard(decimal PortfolioValue, int ActiveAwards, decimal Disbursed, double OnTrackPercentage, int AtRiskCount, int BeneficiariesReached);
|
public record UpdateOrganisationRequest([Required] long OrganisationId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(LabelLength)] string RegistrationNo,
|
||||||
|
[Required][EmailAddress][MaxLength(LabelLength)] string Email,
|
||||||
|
[Required] OrganisationType Type);
|
||||||
|
|
||||||
|
public record CreateOrganisationRequest([Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(LabelLength)] string RegistrationNo,
|
||||||
|
[Required][EmailAddress][MaxLength(LabelLength)] string Email,
|
||||||
|
[Required] OrganisationType Type = OrganisationType.PrivateLimitedCompany);
|
||||||
|
|
||||||
|
public record Dashboard([Required] decimal PortfolioValue,
|
||||||
|
[Required] int ActiveAwards,
|
||||||
|
[Required] decimal Disbursed,
|
||||||
|
[Required] double OnTrackPercentage,
|
||||||
|
[Required] int AtRiskCount,
|
||||||
|
[Required] int BeneficiariesReached);
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Grants;
|
||||||
|
|
||||||
|
public sealed class GrantsCacheService(GrantsService service, IDistributedCache cache) : ServiceCacheBase, IGrantsService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateAwardAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateBudgetAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateContractAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.ExecuteContractAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Award>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getaward-{awardIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetAwardAsync(awardIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Award>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Award[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getawards-{pagination.Page}-{pagination.PageSize}-{pagination.Index}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetAwardsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Award[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Budget>> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Budget>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getbudget-{budgetIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetBudgetAsync(budgetIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Budget>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Budget[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getbudgets-{pagination.Page}-{pagination.PageSize}-{pagination.Index}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetBudgetsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Budget[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Contract>> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Contract>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getctract-{contractIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetContractAsync(contractIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Contract>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Contract[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getcontracts-{pagination.Index}-{pagination.PageSize}-{pagination.Page}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetContractsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Contract[]>());
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
@@ -8,7 +8,7 @@ using PostFundManagement.Infrastructure.Database;
|
|||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Grants;
|
namespace PostFundManagement.Application.Services.Grants;
|
||||||
|
|
||||||
public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IService
|
public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IGrantsService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Contract[]>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Contract[]>>>)(async () =>
|
||||||
@@ -38,9 +38,8 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
var contract = await context.Contracts.AsNoTracking()
|
var contract = await context.Contracts.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return contract is not null
|
return Result.FailIf(contract is null, NotFoundError.New)
|
||||||
? Result.Ok(contract.Map())
|
.ToResult(contract!.Map());
|
||||||
: Result.Fail<Contract>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -71,9 +70,8 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
var budget = await context.Budgets.AsNoTracking()
|
var budget = await context.Budgets.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return budget is not null
|
return Result.FailIf(budget is null, NotFoundError.New)
|
||||||
? Result.Ok(budget.Map())
|
.ToResult(budget!.Map());
|
||||||
: Result.Fail<Budget>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -103,9 +101,8 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
|
|
||||||
var award = await context.Awards.AsNoTracking().FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
var award = await context.Awards.AsNoTracking().FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return award is not null
|
return Result.FailIf(award is null, NotFoundError.New)
|
||||||
? Result.Ok(award.Map())
|
.ToResult(award!.Map());
|
||||||
: Result.Fail<Award>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -121,7 +118,7 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
.SetProperty(u => u.CreatedBy, userId)
|
.SetProperty(u => u.CreatedBy, userId)
|
||||||
.SetProperty(u => u.Status, ContractStatus.Active), cancellationToken);
|
.SetProperty(u => u.Status, ContractStatus.Active), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, UpdateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -148,7 +145,7 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
|
|
||||||
context.Contracts.Add(contract);
|
context.Contracts.Add(contract);
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -173,7 +170,7 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
|
|
||||||
context.Budgets.Add(budget);
|
context.Budgets.Add(budget);
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -202,6 +199,6 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
|||||||
|
|
||||||
context.Awards.Add(award);
|
context.Awards.Add(award);
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
namespace PostFundManagement.Application.Services.Grants;
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
public record CreateAwardRequest(long OrganisationId, long ProgrammeId, decimal Amount);
|
namespace PostFundManagement.Application.Services.Grants;
|
||||||
|
|
||||||
public record CreateBudgetRequest(long ProjectId, string Name, decimal Amount);
|
public record CreateAwardRequest([Required] long OrganisationId, [Required] long ProgrammeId, [Required] decimal Amount);
|
||||||
|
|
||||||
public record CreateContractRequest(long AwardId, decimal TotalValue, DateTime EffectiveAt, DateTime ExpiresAt);
|
public record CreateBudgetRequest([Required] long ProjectId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required] decimal Amount);
|
||||||
|
|
||||||
public record ExecuteContractRequest(long ContractId, string ExternalSignatureId, string SignedDocumentUrl);
|
public record CreateContractRequest([Required] long AwardId,
|
||||||
|
[Required] decimal TotalValue,
|
||||||
|
[Required] DateTime EffectiveAt,
|
||||||
|
[Required] DateTime ExpiresAt);
|
||||||
|
|
||||||
|
public record ExecuteContractRequest([Required] long ContractId,
|
||||||
|
[Required][MaxLength(LabelLength)] string ExternalSignatureId,
|
||||||
|
[Required][MaxLength(LabelLength)] string SignedDocumentUrl);
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
using PostFundManagement.Domain;
|
||||||
|
using PostFundManagement.Domain.Models;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Projects;
|
||||||
|
|
||||||
|
public sealed class ProjectCacheService(ProjectService service, IDistributedCache cache) : ServiceCacheBase, IProjectService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateActivityAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateProgrammeAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.CreateProjectAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Activity[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getactivities-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetActivitiesAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Activity[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Activity>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getactivity-{activityIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetActivityAsync(activityIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Domain.Models.Activity>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Milestone>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getmilestone-{milestoneIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetMilestoneAsync(milestoneIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Milestone>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Milestone[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getmilestones-{pagination.Index}-{pagination.PageSize}-{pagination.Page}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetMilestonesAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Milestone[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprogramme-{programmeIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProgrammeAsync(programmeIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached!.FromJson<Programme>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Project[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprogrammeprojects-{programmeIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProgrammeProjectsAsync(programmeIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Project[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprogrammes-{pagination.Index}-{pagination.PageSize}-{pagination.Page}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProgrammesAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Programme[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Project>> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Project>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getproject-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Project>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Indicator[]>> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Indicator[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprojectindicators-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectIndicatorsAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if(result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Indicator[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Milestone[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprojectmilestones-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectMilestonesAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if(result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Milestone[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Risk[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprojectrisks-{projectIdHash}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectRisksAsync(projectIdHash, cancellationToken);
|
||||||
|
|
||||||
|
if(result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Risk[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Project[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"getprojects-{pagination.Index}-{pagination.Page}-{pagination.PageSize}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = await service.GetProjectsAsync(pagination, cancellationToken);
|
||||||
|
|
||||||
|
if(result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached.FromJson<Project[]>());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateMilestoneAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateProgrammeAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateProjectAsync(request, userId, cancellationToken);
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await service.UpdateProjectStatusAsync(request, userId, cancellationToken);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Abstractions;
|
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
using PostFundManagement.Domain.Extensions;
|
using PostFundManagement.Domain.Extensions;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
@@ -8,32 +8,24 @@ using PostFundManagement.Infrastructure.Database;
|
|||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Projects;
|
namespace PostFundManagement.Application.Services.Projects;
|
||||||
|
|
||||||
public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IService
|
public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IProjectService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken);
|
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||||
|
|
||||||
if (project == null)
|
|
||||||
return Result.Fail(NotFoundError.New);
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
project.Status = request.Status;
|
|
||||||
project.UpdatedBy = userId;
|
|
||||||
project.UpdatedAt = DateTime.UtcNow;
|
|
||||||
|
|
||||||
context.Projects.Update(project);
|
|
||||||
|
|
||||||
var changedTotal = await context.Projects
|
var changedTotal = await context.Projects
|
||||||
.Where(p => p.Id != request.ProjectId)
|
.Where(f => f.Id == request.ProjectId)
|
||||||
.ExecuteUpdateAsync(f => f
|
.ExecuteUpdateAsync(f => f
|
||||||
.SetProperty(p => p.Status, request.Status)
|
.SetProperty(p => p.Status, request.Status)
|
||||||
.SetProperty(p => p.UpdatedBy, userId)
|
.SetProperty(p => p.UpdatedBy, userId)
|
||||||
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
.SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal == 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Project[]>> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -63,9 +55,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
var project = await context.Projects.AsNoTracking()
|
var project = await context.Projects.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return project is not null
|
return Result.FailIf(project is null, NotFoundError.New)
|
||||||
? Result.Ok(project.Map())
|
.ToResult(project!.Map());
|
||||||
: Result.Fail<Project>(NotFoundError.Create());
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Domain.Models.Activity[]>> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
@@ -91,17 +82,21 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
{
|
{
|
||||||
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
if (await context.Projects.AnyAsync(p => p.Name == request.Name && p.Sector == request.Sector && p.ProjectType == request.ProjectType, cancellationToken))
|
var name = request.Name.Humanize(LetterCasing.Title).Trim();
|
||||||
|
|
||||||
|
if (await context.Projects.AnyAsync(p => p.Name == name && p.Sector == request.Sector && p.ProjectType == request.ProjectType, cancellationToken))
|
||||||
return Result.Fail(ValidationError.Create("A similarly named project exists of the same project type and sector"));
|
return Result.Fail(ValidationError.Create("A similarly named project exists of the same project type and sector"));
|
||||||
|
|
||||||
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
||||||
return Result.Fail(NotFoundError.New);
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
|
var description = request.Description.Humanize(LetterCasing.Sentence).Trim();
|
||||||
|
|
||||||
context.Projects.Add(new Domain.Entities.Project
|
context.Projects.Add(new Domain.Entities.Project
|
||||||
{
|
{
|
||||||
ProgrammeId = request.ProgrammeId,
|
ProgrammeId = request.ProgrammeId,
|
||||||
Name = request.Name,
|
Name = name,
|
||||||
Description = request.Description,
|
Description = description,
|
||||||
Sector = request.Sector,
|
Sector = request.Sector,
|
||||||
ProjectType = request.ProjectType,
|
ProjectType = request.ProjectType,
|
||||||
Status = ApprovalStatus.Pending,
|
Status = ApprovalStatus.Pending,
|
||||||
@@ -109,7 +104,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0,
|
||||||
|
CreateError.Create("No changes detected, update cancelled."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
@@ -128,9 +124,9 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
{
|
{
|
||||||
ProjectId = request.ProjectId,
|
ProjectId = request.ProjectId,
|
||||||
MilestoneId = request.MilestoneId,
|
MilestoneId = request.MilestoneId,
|
||||||
Category = request.Category,
|
Category = request.Category.Humanize(LetterCasing.Title).Trim(),
|
||||||
Amount = request.Amount,
|
Amount = request.Amount,
|
||||||
Title = request.Title,
|
Title = request.Title.Humanize(LetterCasing.Title).Trim(),
|
||||||
Description = request.Description,
|
Description = request.Description,
|
||||||
PerformedAt = request.PerformedAt,
|
PerformedAt = request.PerformedAt,
|
||||||
Status = ApprovalStatus.Pending,
|
Status = ApprovalStatus.Pending,
|
||||||
@@ -138,7 +134,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
CreatedAt = DateTime.UtcNow
|
CreatedAt = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0,
|
||||||
|
CreateError.Create("No record was created."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Milestone[]>> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
@@ -179,7 +176,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
return Result.Ok(evidences.Select(e => e.Map()).ToArray());
|
return Result.Ok(evidences.Select(e => e.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Milestone>> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Milestone>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Milestone>>>)(async () =>
|
||||||
{
|
{
|
||||||
var decodeResult = hashService.DecodeLongIdHash(milestoneIdHash);
|
var decodeResult = hashService.DecodeLongIdHash(milestoneIdHash);
|
||||||
@@ -192,9 +189,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
var milestone = await context.Milestones.AsNoTracking()
|
var milestone = await context.Milestones.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return milestone is not null
|
return Result.FailIf(milestone is null, NotFoundError.New)
|
||||||
? Result.Ok(milestone.Map())
|
.ToResult(milestone!.Map());
|
||||||
: Result.Fail<Milestone>(NotFoundError.Create());
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
@@ -211,32 +207,35 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
return Result.Ok(milestones.Select(i => i.Map()).ToArray());
|
return Result.Ok(milestones.Select(i => i.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result> UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
var changedTotal = await context.Milestones.Where(m => m.Id == request.MilestoneId)
|
var changedTotal = await context.Milestones.Where(m => m.Id == request.MilestoneId)
|
||||||
.ExecuteUpdateAsync(f => f
|
.ExecuteUpdateAsync(f => f
|
||||||
.SetProperty(u => u.Name, request.Name)
|
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||||
.SetProperty(u => u.DueAt, request.DueAt)
|
.SetProperty(u => u.DueAt, request.DueAt)
|
||||||
.SetProperty(u => u.Priority, request.Priority)
|
.SetProperty(u => u.Priority, request.Priority)
|
||||||
.SetProperty(u => u.Status, request.Status)
|
.SetProperty(u => u.Status, request.Status)
|
||||||
.SetProperty(u => u.UpdatedBy, userId)
|
.SetProperty(u => u.UpdatedBy, userId)
|
||||||
.SetProperty(u => u.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
.SetProperty(u => u.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result> UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
{
|
{
|
||||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
var changedTotal = await context.Projects.Where(p => p.Id == request.ProjectId)
|
var changedTotal = await context.Projects.Where(p => p.Id == request.ProjectId)
|
||||||
.ExecuteUpdateAsync(f => f
|
.ExecuteUpdateAsync(f => f
|
||||||
.SetProperty(u => u.Name, request.Name)
|
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||||
.SetProperty(u => u.Description, request.Description)
|
.SetProperty(u => u.Description, request.Description.Humanize(LetterCasing.Sentence).Trim())
|
||||||
.SetProperty(u => u.StartedAt, request.StartedAt)
|
.SetProperty(u => u.StartedAt, request.StartedAt)
|
||||||
.SetProperty(u => u.EndedAt, request.EndedAt)
|
.SetProperty(u => u.EndedAt, request.EndedAt)
|
||||||
.SetProperty(u => u.UpdatedBy, userId)
|
.SetProperty(u => u.UpdatedBy, userId)
|
||||||
@@ -244,10 +243,10 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
.SetProperty(u => u.Sector, request.Sector)
|
.SetProperty(u => u.Sector, request.Sector)
|
||||||
.SetProperty(u => u.ProjectType, request.ProjectType), cancellationToken);
|
.SetProperty(u => u.ProjectType, request.ProjectType), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<Domain.Models.Activity>> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result<Domain.Models.Activity>>>)(async () =>
|
await Result.Try((Func<ValueTask<Result<Domain.Models.Activity>>>)(async () =>
|
||||||
{
|
{
|
||||||
var decodeResult = hashService.DecodeLongIdHash(activityIdHash);
|
var decodeResult = hashService.DecodeLongIdHash(activityIdHash);
|
||||||
@@ -261,9 +260,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
var activity = await context.Activities.AsNoTracking()
|
var activity = await context.Activities.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
.FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
return activity is not null
|
return Result.FailIf(activity is null, NotFoundError.New)
|
||||||
? Result.Ok(activity.Map())
|
.ToResult(activity!.Map());
|
||||||
: Result.Fail<Domain.Models.Activity>(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<Risk[]>> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default) =>
|
||||||
@@ -284,4 +282,100 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
|||||||
|
|
||||||
return Result.Ok(risks.Select(i => i.Map()).ToArray());
|
return Result.Ok(risks.Select(i => i.Map()).ToArray());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var name = request.Name.Humanize(LetterCasing.Title).Trim();
|
||||||
|
|
||||||
|
if (!await context.Portfolios.AnyAsync(p => p.Id == request.PortfolioId, cancellationToken))
|
||||||
|
return Result.Fail(ValidationError.Create("Portfolio does not exist."));
|
||||||
|
|
||||||
|
if (await context.Programmes.AnyAsync(p => p.Name == name, cancellationToken))
|
||||||
|
return Result.Fail(ValidationError.Create("Programme of the same name exists"));
|
||||||
|
|
||||||
|
context.Programmes.Add(new Domain.Entities.Programme
|
||||||
|
{
|
||||||
|
PortfolioId = request.PortfolioId,
|
||||||
|
OwnedBy = request.OwnerId,
|
||||||
|
Name = name,
|
||||||
|
CreatedBy = userId,
|
||||||
|
CreatedAt = DateTime.UtcNow,
|
||||||
|
Status = ApprovalStatus.Pending
|
||||||
|
});
|
||||||
|
|
||||||
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result> UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
|
var changedTotal = await context.Programmes.Where(p => p.Id == request.ProgrammeId)
|
||||||
|
.ExecuteUpdateAsync(f => f
|
||||||
|
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||||
|
.SetProperty(u => u.OwnedBy, request.OwnerId)
|
||||||
|
.SetProperty(u => u.Status, request.Status)
|
||||||
|
.SetProperty(u => u.UpdatedBy, userId)
|
||||||
|
.SetProperty(u => u.UpdatedAt, DateTime.UtcNow), cancellationToken);
|
||||||
|
|
||||||
|
return Result.OkIf(changedTotal > 0, UpdateError.Create("No changes detected, update cancelled."));
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme>> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme>>>)(async () =>
|
||||||
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(programmeIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var programme = await context.Programmes.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
|
||||||
|
|
||||||
|
return Result.FailIf(programme is null, NotFoundError.New)
|
||||||
|
.ToResult(programme!.Map());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Programme[]>> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Programme[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
var programmes = await context.Programmes.AsNoTracking()
|
||||||
|
.OrderByDescending(m => m.CreatedAt)
|
||||||
|
.Skip((pagination.Page - 1) * pagination.PageSize)
|
||||||
|
.Take(pagination.PageSize)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(programmes.Select(i => i.Map()).ToArray());
|
||||||
|
}));
|
||||||
|
|
||||||
|
public async ValueTask<Result<Project[]>> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<Project[]>>>)(async () =>
|
||||||
|
{
|
||||||
|
var decodeResult = hashService.DecodeLongIdHash(programmeIdHash);
|
||||||
|
|
||||||
|
if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create()
|
||||||
|
.CausedBy(decodeResult.Errors));
|
||||||
|
|
||||||
|
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!await context.Programmes.AnyAsync(p => p.Id == decodeResult.Value, cancellationToken))
|
||||||
|
return Result.Fail(NotFoundError.New);
|
||||||
|
|
||||||
|
var projects = await context.Projects.AsNoTracking()
|
||||||
|
.Where(p => p.ProgrammeId == decodeResult.Value)
|
||||||
|
.OrderByDescending(o => o.CreatedAt)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Ok(projects.Select(i => i.Map()).ToArray());
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,40 @@
|
|||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
|
using static PostFundManagement.Domain.Extensions.Constants;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Projects;
|
namespace PostFundManagement.Application.Services.Projects;
|
||||||
|
|
||||||
public record UpdateProjectRequest(long ProjectId, string Name, string Description, DateTime StartedAt, DateTime? EndedAt, IndustrySector Sector, ProjectType ProjectType);
|
public record UpdateProgrammeRequest([Required] long ProgrammeId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required] Guid OwnerId,
|
||||||
|
[Required] ApprovalStatus Status);
|
||||||
|
|
||||||
public record UpdateMilestoneRequest(long MilestoneId, DateTime DueAt, string Name, Priority Priority, ApprovalStatus Status);
|
public record CreateProgrammeRequest(long PortfolioId, string Name, Guid OwnerId);
|
||||||
|
|
||||||
public record UpdateProjectStatusRequest(long ProjectId, ApprovalStatus Status);
|
public record UpdateProjectRequest([Required] long ProjectId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description,
|
||||||
|
DateTime? StartedAt, DateTime? EndedAt,
|
||||||
|
[Required] IndustrySector Sector,
|
||||||
|
[Required] ProjectType ProjectType);
|
||||||
|
|
||||||
public record CreateProjectRequest(long ProgrammeId, string Name, string Description, IndustrySector Sector, ProjectType ProjectType);
|
public record UpdateMilestoneRequest([Required] long MilestoneId,
|
||||||
|
[Required] DateTime DueAt,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required] Priority Priority,
|
||||||
|
[Required] ApprovalStatus Status);
|
||||||
|
|
||||||
public record CreateActivityRequest(long ProjectId, long? MilestoneId, string Category, decimal Amount, string Title, string Description, DateTime? PerformedAt);
|
public record UpdateProjectStatusRequest([Required] long ProjectId, [Required] ApprovalStatus Status);
|
||||||
|
|
||||||
|
public record CreateProjectRequest([Required] long ProgrammeId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Name,
|
||||||
|
[Required][MaxLength(TextLength)] string Description,
|
||||||
|
[Required] IndustrySector Sector,
|
||||||
|
[Required] ProjectType ProjectType);
|
||||||
|
|
||||||
|
public record CreateActivityRequest([Required] long ProjectId,
|
||||||
|
[Required] long? MilestoneId,
|
||||||
|
[Required][MaxLength(LabelLength)] string Category,
|
||||||
|
[Required] decimal Amount,
|
||||||
|
[Required][MaxLength(LabelLength)] string Title,
|
||||||
|
[Required][MaxLength(TextLength)] string Description,
|
||||||
|
DateTime? PerformedAt);
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ public sealed class BrowserLocalStorageService(ProtectedLocalStorage storage)
|
|||||||
{
|
{
|
||||||
var retrieval = await storage.GetAsync<string>(key);
|
var retrieval = await storage.GetAsync<string>(key);
|
||||||
|
|
||||||
return retrieval.Success && !string.IsNullOrWhiteSpace(retrieval.Value)
|
return Result.FailIf(!retrieval.Success && string.IsNullOrWhiteSpace(retrieval.Value),
|
||||||
? Result.Ok(retrieval.Value)
|
NotFoundError.Create($"Could not find object by key {key}"))
|
||||||
: Result.Fail(NotFoundError.Create($"Could not find object by key {key}"));
|
.ToResult(retrieval.Value!);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<TValue>> GetAsync<TValue>(string key) where TValue : class =>
|
public async ValueTask<Result<TValue>> GetAsync<TValue>(string key) where TValue : class =>
|
||||||
@@ -43,8 +43,8 @@ public sealed class BrowserLocalStorageService(ProtectedLocalStorage storage)
|
|||||||
{
|
{
|
||||||
var retrieval = await storage.GetAsync<TValue>(key);
|
var retrieval = await storage.GetAsync<TValue>(key);
|
||||||
|
|
||||||
return retrieval.Success && retrieval.Value is not null
|
return Result.FailIf(!retrieval.Success && retrieval.Value is null,
|
||||||
? Result.Ok(retrieval.Value)
|
NotFoundError.Create($"Could not find object by key {key}"))
|
||||||
: Result.Fail(NotFoundError.Create($"Could not find object by key {key}"));
|
.ToResult(retrieval.Value!);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,16 @@
|
|||||||
using PostFundManagement.Domain.Abstractions;
|
using PostFundManagement.Domain.Abstractions;
|
||||||
using PostFundManagement.Domain.Errors;
|
using PostFundManagement.Domain.Errors;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Shared;
|
namespace PostFundManagement.Application.Services.Shared;
|
||||||
|
|
||||||
public sealed partial class HashService(IHashids hasher) : IService
|
public sealed partial class HashService(IHashids hasher) : IService
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"\A\b[0-9a-fA-F]+\b\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
|
||||||
private static partial Regex HexHashRegex { get; }
|
|
||||||
|
|
||||||
[GeneratedRegex(@"\A[0-9a-fA-F]{32}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
|
||||||
private static partial Regex Md5Regex { get; }
|
|
||||||
|
|
||||||
[GeneratedRegex(@"\A[0-9a-fA-F]{64}\Z", RegexOptions.None, matchTimeoutMilliseconds: 100)]
|
|
||||||
private static partial Regex Sha256Regex { get; }
|
|
||||||
|
|
||||||
public static bool IsMd5Hash(string? value) =>
|
public static bool IsMd5Hash(string? value) =>
|
||||||
!string.IsNullOrWhiteSpace(value) && Md5Regex.IsMatch(value);
|
!string.IsNullOrWhiteSpace(value) && Patterns.Md5Regex.IsMatch(value);
|
||||||
|
|
||||||
public static bool IsSha256Hash(string? value) =>
|
public static bool IsSha256Hash(string? value) =>
|
||||||
!string.IsNullOrWhiteSpace(value) && Sha256Regex.IsMatch(value);
|
!string.IsNullOrWhiteSpace(value) && Patterns.Sha256Regex.IsMatch(value);
|
||||||
|
|
||||||
public static string? StringToSha256Hash(string? input) =>
|
public static string? StringToSha256Hash(string? input) =>
|
||||||
string.IsNullOrEmpty(input) ? null : Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input)));
|
string.IsNullOrEmpty(input) ? null : Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(input)));
|
||||||
@@ -39,17 +31,18 @@ public sealed partial class HashService(IHashids hasher) : IService
|
|||||||
return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant());
|
return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<string> HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input)
|
public Result<string> HashEncodeHex(string input) =>
|
||||||
? Result.Fail<string>(ValidationError.Create("Input must be a valid hexadecimal string."))
|
Result.FailIf(string.IsNullOrWhiteSpace(input) || !Patterns.HexHashRegex.IsMatch(input),
|
||||||
: Result.Ok(hasher.EncodeHex(input));
|
ValidationError.Create("Input must be a valid hexadecimal string."))
|
||||||
|
.ToResult(hasher.EncodeHex(input));
|
||||||
|
|
||||||
public Result<string> HashEncodeIntId(int id) => id < 0
|
public Result<string> HashEncodeIntId(int id) =>
|
||||||
? Result.Fail<string>(ValidationError.Create("Id cannot be negative."))
|
Result.FailIf(id < 0, ValidationError.Create("Id cannot be negative."))
|
||||||
: Result.Ok(hasher.Encode(id));
|
.ToResult(hasher.Encode(id));
|
||||||
|
|
||||||
public Result<string> HashEncodeLongId(long id) => id < 0
|
public Result<string> HashEncodeLongId(long id) =>
|
||||||
? Result.Fail<string>(ValidationError.Create("Id cannot be negative."))
|
Result.FailIf(id < 0, ValidationError.Create("Id cannot be negative."))
|
||||||
: Result.Ok(hasher.EncodeLong(id));
|
.ToResult(hasher.EncodeLong(id));
|
||||||
|
|
||||||
public Result<int> DecodeIntIdHash(string hash)
|
public Result<int> DecodeIntIdHash(string hash)
|
||||||
{
|
{
|
||||||
@@ -58,9 +51,8 @@ public sealed partial class HashService(IHashids hasher) : IService
|
|||||||
|
|
||||||
int[] decoded = hasher.Decode(hash);
|
int[] decoded = hasher.Decode(hash);
|
||||||
|
|
||||||
return decoded.Length == 1
|
return Result.FailIf(decoded.Length <= 0, ValidationError.Create("Invalid or modified Int hash token."))
|
||||||
? Result.Ok(decoded[0]) :
|
.ToResult(decoded[0]);
|
||||||
Result.Fail<int>(ValidationError.Create("Invalid or modified Int hash token."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result<long> DecodeLongIdHash(string hash)
|
public Result<long> DecodeLongIdHash(string hash)
|
||||||
@@ -70,8 +62,8 @@ public sealed partial class HashService(IHashids hasher) : IService
|
|||||||
|
|
||||||
long[] decoded = hasher.DecodeLong(hash);
|
long[] decoded = hasher.DecodeLong(hash);
|
||||||
|
|
||||||
return decoded.Length == 1 ? Result.Ok(decoded[0]) :
|
return Result.FailIf(decoded.Length <= 0, ValidationError.Create("Invalid or modified Long hash token."))
|
||||||
Result.Fail<long>(ValidationError.Create("Invalid or modified Long hash token."));
|
.ToResult(decoded[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<string>> DecodeHexHash(string hex) =>
|
public async Task<Result<string>> DecodeHexHash(string hex) =>
|
||||||
@@ -79,8 +71,7 @@ public sealed partial class HashService(IHashids hasher) : IService
|
|||||||
{
|
{
|
||||||
string decoded = hasher.DecodeHex(hex);
|
string decoded = hasher.DecodeHex(hex);
|
||||||
|
|
||||||
return string.IsNullOrEmpty(decoded)
|
return Result.FailIf(string.IsNullOrEmpty(decoded), ValidationError.Create("Invalid or corrupted hex hash."))
|
||||||
? Result.Fail<string>(ValidationError.Create("Invalid or corrupted hex hash."))
|
.ToResult(decoded);
|
||||||
: Result.Ok(decoded);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1,31 +1,10 @@
|
|||||||
using PostFundManagement.Application.Abstractions;
|
using PostFundManagement.Application.Abstractions;
|
||||||
using PostFundManagement.Application.Abstractions.Services;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
using PostFundManagement.Domain.Errors;
|
|
||||||
|
|
||||||
namespace PostFundManagement.Application.Services.Shared;
|
namespace PostFundManagement.Application.Services.Shared;
|
||||||
|
|
||||||
public sealed class IdentityCacheService(IdentityService service, IDistributedCache cache) : ServiceCacheBase, IIdentityService
|
public sealed class IdentityCacheService(IdentityService service) : ServiceCacheBase, IIdentityService
|
||||||
{
|
{
|
||||||
public async ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) =>
|
||||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
await service.UserIsAuthorisedAsync(userId, cancellationToken);
|
||||||
{
|
|
||||||
var cacheKey = HashService.ToMd5Hash($"userisauthorised-{userId}").Value;
|
|
||||||
|
|
||||||
var cached = await cache.GetStringAsync(cacheKey);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(cached))
|
|
||||||
{
|
|
||||||
var result = await service.UserIsAuthorisedAsync(userId, cancellationToken); ;
|
|
||||||
|
|
||||||
if (result.IsFailed) return result;
|
|
||||||
|
|
||||||
await cache.SetStringAsync(cacheKey, "true", cacheOptions, cancellationToken);
|
|
||||||
|
|
||||||
return Result.Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cached.Equals("true", StringComparison.OrdinalIgnoreCase)
|
|
||||||
? Result.Ok()
|
|
||||||
: Result.Fail(NotFoundError.New);
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ public sealed class IdentityService(IDbContextFactory<ApplicationDbContext> cont
|
|||||||
var userExistsAndActive = await context.Users
|
var userExistsAndActive = await context.Users
|
||||||
.AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken);
|
.AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(!userExistsAndActive, NotFoundError.Create());
|
return Result.OkIf(userExistsAndActive, NotFoundError.Create());
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,16 +29,16 @@ public sealed class TokenService(ISecurityConnectApi connectApi, IOptions<Securi
|
|||||||
{
|
{
|
||||||
var tokenResponse = JsonSerializer.Deserialize<Domain.Security.Models.TokenResponse>(contentRaw);
|
var tokenResponse = JsonSerializer.Deserialize<Domain.Security.Models.TokenResponse>(contentRaw);
|
||||||
|
|
||||||
return !string.IsNullOrWhiteSpace(tokenResponse?.AccessToken)
|
return Result.FailIf(string.IsNullOrWhiteSpace(tokenResponse?.AccessToken),
|
||||||
? Result.Ok(tokenResponse)
|
NotFoundError.Create("Authentication succeeded, but no access token was found in the response payload."))
|
||||||
: Result.Fail<Domain.Security.Models.TokenResponse>(NotFoundError.Create("Authentication succeeded, but no access token was found in the response payload."));
|
.ToResult(tokenResponse!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await Result.Try((Func<ValueTask<Result<Domain.Security.Models.TokenResponse>>>)(async () =>
|
return await Result.Try((Func<ValueTask<Result<Domain.Security.Models.TokenResponse>>>)(async () =>
|
||||||
{
|
{
|
||||||
var errorResult = JsonSerializer.Deserialize<TokenErrorResponse>(contentRaw);
|
var errorResult = JsonSerializer.Deserialize<TokenErrorResponse>(contentRaw);
|
||||||
|
|
||||||
return Result.FailIf(errorResult != null,
|
return Result.OkIf(errorResult is not null,
|
||||||
CreateError.Create($"{errorResult!.Error}: {errorResult.ErrorDescription}"));
|
CreateError.Create($"{errorResult!.Error}: {errorResult.ErrorDescription}"));
|
||||||
}));
|
}));
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions;
|
||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Tools;
|
||||||
|
|
||||||
|
public sealed class ToolCacheService(ToolService service, IDistributedCache cache) : ServiceCacheBase, IToolService
|
||||||
|
{
|
||||||
|
public async ValueTask<Result<string>> EncodeLong(long id, CancellationToken cancellationToken) =>
|
||||||
|
await Result.Try((Func<ValueTask<Result<string>>>)(async () =>
|
||||||
|
{
|
||||||
|
var cacheKey = HashService.ToMd5Hash($"encodelonghash-{id}").Value;
|
||||||
|
|
||||||
|
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||||
|
|
||||||
|
if(string.IsNullOrWhiteSpace(cached))
|
||||||
|
{
|
||||||
|
var result = (await service.EncodeLong(id).ConfigureAwait(false));
|
||||||
|
|
||||||
|
if (result.IsFailed) return result;
|
||||||
|
|
||||||
|
await cache.SetStringAsync(cacheKey, result.Value, cacheOptions, cancellationToken);
|
||||||
|
|
||||||
|
return result.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Ok(cached);
|
||||||
|
}));
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Services.Shared;
|
||||||
|
|
||||||
|
namespace PostFundManagement.Application.Services.Tools;
|
||||||
|
|
||||||
|
public sealed class ToolService(HashService service) : IToolService
|
||||||
|
{
|
||||||
|
public ValueTask<Result<string>> EncodeLong(long id, CancellationToken cancellationToken = default) =>
|
||||||
|
ValueTask.FromResult(service.HashEncodeLongId(id));
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using PostFundManagement.Application.Abstractions;
|
using PostFundManagement.Application.Abstractions;
|
||||||
using PostFundManagement.Application.Abstractions.Services;
|
using PostFundManagement.Application.Abstractions.Services;
|
||||||
|
using PostFundManagement.Application.Extensions;
|
||||||
using PostFundManagement.Application.Services.Shared;
|
using PostFundManagement.Application.Services.Shared;
|
||||||
using PostFundManagement.Domain;
|
using PostFundManagement.Domain;
|
||||||
using PostFundManagement.Domain.Models;
|
using PostFundManagement.Domain.Models;
|
||||||
@@ -21,14 +22,12 @@ public sealed class UserCacheService(UserService service, IDistributedCache cach
|
|||||||
|
|
||||||
if (userResult.IsFailed) return userResult;
|
if (userResult.IsFailed) return userResult;
|
||||||
|
|
||||||
await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(userResult.Value), cacheOptions, cancellationToken);
|
await cache.SetStringAsync(cacheKey, userResult.Value.ToJson(), cacheOptions, cancellationToken);
|
||||||
|
|
||||||
return userResult;
|
return userResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
var restored = JsonSerializer.Deserialize<User>(cached)!;
|
return Result.Ok(cached.FromJson<User>());
|
||||||
|
|
||||||
return Result.Ok(restored);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) =>
|
||||||
@@ -45,15 +44,13 @@ public sealed class UserCacheService(UserService service, IDistributedCache cach
|
|||||||
|
|
||||||
if (usersResult.IsFailed) return usersResult;
|
if (usersResult.IsFailed) return usersResult;
|
||||||
|
|
||||||
await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(usersResult.Value),
|
await cache.SetStringAsync(cacheKey, usersResult.Value.ToJson(),
|
||||||
cacheOptions, cancellationToken);
|
cacheOptions, cancellationToken);
|
||||||
|
|
||||||
return usersResult;
|
return usersResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
var restored = JsonSerializer.Deserialize<User[]>(cached)!;
|
return Result.Ok(cached.FromJson<User[]>());
|
||||||
|
|
||||||
return Result.Ok(restored);
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) =>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
|||||||
context.Users.Update(user);
|
context.Users.Update(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New);
|
return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) =>
|
public async ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) =>
|
||||||
@@ -74,7 +74,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
|||||||
.Where(f => f.Id == userId)
|
.Where(f => f.Id == userId)
|
||||||
.ExecuteUpdateAsync(f => f.SetProperty(u => u.Status, status), cancellationToken);
|
.ExecuteUpdateAsync(f => f.SetProperty(u => u.Status, status), cancellationToken);
|
||||||
|
|
||||||
return Result.FailIf(changedTotal <= 0, UpdateError.New);
|
return Result.OkIf(changedTotal > 0, UpdateError.New);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
public async ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken) =>
|
public async ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken) =>
|
||||||
@@ -85,8 +85,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
|||||||
var user = await context.Users.AsNoTracking()
|
var user = await context.Users.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(f => f.Id == userId, cancellationToken);
|
.FirstOrDefaultAsync(f => f.Id == userId, cancellationToken);
|
||||||
|
|
||||||
return user is not null
|
return Result.FailIf(user is null, NotFoundError.New)
|
||||||
? Result.Ok(user.Map())
|
.ToResult(user!.Map());
|
||||||
: Result.Fail(NotFoundError.New);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# GEMINI_API_KEY: Required for Gemini AI API calls.
|
||||||
|
# AI Studio automatically injects this at runtime from user secrets.
|
||||||
|
# Users configure this via the Secrets panel in the AI Studio UI.
|
||||||
|
GEMINI_API_KEY="MY_GEMINI_API_KEY"
|
||||||
|
|
||||||
|
# APP_URL: The URL where this applet is hosted.
|
||||||
|
# AI Studio automatically injects this at runtime with the Cloud Run service URL.
|
||||||
|
# Used for self-referential links, OAuth callbacks, and API endpoints.
|
||||||
|
APP_URL="MY_APP_URL"
|
||||||
|
|
||||||
|
# OIDC SecuritySettings
|
||||||
|
VITE_OIDC_AUTHORITY="https://sts.security.khongisa.co.za"
|
||||||
|
VITE_OIDC_AUDIENCE="pfm-api-dev"
|
||||||
|
VITE_OIDC_CLIENT_ID="pfm-scalar-dev"
|
||||||
|
VITE_OIDC_CLIENT_SECRET="secret_71f88570a46eb22eb5270b47f69e3332c84d8db85d008f0276cb7431c8872d27"
|
||||||
|
|
||||||
|
# PFM Backend API Base URL
|
||||||
|
VITE_API_BASE_URL="https://api.ifri.khongisa.co.za/"
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
coverage/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Stage 1: Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Enable corepack to use Yarn
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
# Copy dependency definitions from the subdirectory
|
||||||
|
COPY PostFundManagement.ConsoleUI/package*.json PostFundManagement.ConsoleUI/yarn.lock* ./
|
||||||
|
|
||||||
|
# Install dependencies via Yarn
|
||||||
|
RUN yarn install --frozen-lockfile || yarn install
|
||||||
|
|
||||||
|
# Copy application source from the subdirectory
|
||||||
|
COPY PostFundManagement.ConsoleUI/ ./
|
||||||
|
|
||||||
|
# Build Vite application for production
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
# Stage 2: Production Nginx Server
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx configuration from stage 1 build directory
|
||||||
|
COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Copy compiled static assets from build stage
|
||||||
|
COPY --from=builder /app/dist ./
|
||||||
|
|
||||||
|
# Copy entrypoint script for dynamic runtime environment variables
|
||||||
|
COPY --from=builder /app/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Expose port 3000
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Set entrypoint and default command
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user