Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea0e4eb018 | ||
|
|
9ebbafb840 | ||
|
|
f3863c785d | ||
|
|
9874eac65d | ||
|
|
2b8edd05e3 | ||
|
|
edbae6defb | ||
|
|
b3e884336d | ||
|
|
1b0ceb8ea9 | ||
|
|
d779e4152e | ||
|
|
9e611cb79c | ||
|
|
b99ebf657c | ||
|
|
99ab5b2ce6 | ||
|
|
a15b25248d | ||
|
|
66f720a20b | ||
|
|
81c111c3c3 | ||
|
|
415aa30cf7 | ||
|
|
72ea20a2de | ||
|
|
15661ce8cb | ||
|
|
e274c33592 | ||
|
|
a218b19875 | ||
|
|
0a1378fe25 | ||
|
|
6719699689 | ||
|
|
8ea014e4fb | ||
|
|
9728459223 |
@@ -14,9 +14,9 @@ public sealed class GetApplicantInviteEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetApplicantInviteAsync(userId, cancellationToken) switch
|
||||
await service.GetApplicantInviteAsync(applicantUserId, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true, Value: var invite } => Results.Ok(invite),
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public sealed class GetIndicatorEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.WithTags(EndpointTags.Evidence);
|
||||
.WithTags(EndpointTags.Evidence)
|
||||
.CacheOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public class GetIndicatorsEndpoint : IEndpoint
|
||||
.Produces<Indicator[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.WithTags(EndpointTags.Evidence);
|
||||
.WithTags(EndpointTags.Evidence)
|
||||
.CacheOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public class GetProjectEvidenceEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Execution;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class ApproveDisbursementEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Execution;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateDisbursementEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Execution;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -13,7 +14,7 @@ public class CreateRiskEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Execution;
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public sealed class GetDisbursementEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetDisbursementAsync(disbursementIdHash: disbursementId, cancellationToken) switch
|
||||
{
|
||||
@@ -31,6 +31,7 @@ public sealed class GetDisbursementEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces<Problems>(StatusCodes.Status400BadRequest)
|
||||
.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.Services.Execution;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetDisbursementsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -28,6 +28,7 @@ public class GetDisbursementsEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.WithTags(EndpointTags.Execution);
|
||||
.WithTags(EndpointTags.Execution)
|
||||
.CacheOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Execution;
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public sealed class GetRiskEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetRiskAsync(riskIdHash: riskId, cancellationToken) switch
|
||||
{
|
||||
@@ -30,6 +30,7 @@ public sealed class GetRiskEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Execution;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -13,7 +13,7 @@ public class GetRisksEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -26,6 +26,7 @@ public class GetRisksEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
@@ -14,7 +15,7 @@ public sealed class UpdateDisbursementEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ 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.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
@@ -14,7 +15,7 @@ public sealed class UpdateRiskEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPut("api/execution/risks", async (UpdateRiskRequest request,
|
||||
ExecutionService service, CancellationToken cancellationToken = default) =>
|
||||
IExecutionService service, CancellationToken cancellationToken = default) =>
|
||||
await service.UpdateRiskAsync(request, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public sealed class CreateOrganisationEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ 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.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -12,7 +13,7 @@ public class DashboardEndpoint : IEndpoint
|
||||
{
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapGet("api/governance/dashboard", async (GovernanceService service,
|
||||
builder.MapGet("api/governance/dashboard", async (IGovernanceService service,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await service.GetDashboardAsync(cancellationToken) switch
|
||||
{
|
||||
@@ -25,6 +26,7 @@ public class DashboardEndpoint : IEndpoint
|
||||
.Produces<Dashboard>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
using PostFundManagement.Domain.Security;
|
||||
@@ -13,19 +14,22 @@ public class GetInstrumentsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetInstrumentsAsync(cancellationToken) switch
|
||||
await service.GetInstrumentsAsync(programmeId, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs),
|
||||
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems()),
|
||||
})
|
||||
.WithDescription("Get all funding instruments")
|
||||
.WithName(typeof(GetInstrumentsEndpoint).ToEndpointName())
|
||||
.MapToApiVersion(new ApiVersion(1))
|
||||
.Produces<Instrument[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.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.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -15,10 +15,10 @@ public class GetOrganisationEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ 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<NotFoundError>() => Results.NotFound(),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||
@@ -31,6 +31,7 @@ public class GetOrganisationEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.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.Services.Governance;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetOrganisationsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -27,6 +27,7 @@ public class GetOrganisationsEndpoint : IEndpoint
|
||||
.Produces<Organisation[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
using PostFundManagement.Domain.Security;
|
||||
@@ -13,19 +14,22 @@ public class GetPortfoliosEndpoint : IEndpoint
|
||||
{
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapGet("api/governance/portfolios", async (GovernanceService service,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
await service.GetPorfoliosAsync(cancellationToken) switch
|
||||
builder.MapGet("api/governance/organisations/{organisationId}/portfolio", async (string organisationId,
|
||||
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||
await service.GetPorfoliosAsync(organisationId, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
|
||||
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||
})
|
||||
.WithDescription("Get all portfolios")
|
||||
.WithName(typeof(GetPortfoliosEndpoint).ToEndpointName())
|
||||
.MapToApiVersion(new ApiVersion(1))
|
||||
.Produces<Portfolio[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.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.Services.Governance;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
using PostFundManagement.Domain.Security;
|
||||
@@ -13,19 +14,22 @@ public class GetProgrammesEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetProgrammesAsync(cancellationToken) switch
|
||||
await service.GetProgrammesAsync(portfolioId, cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true, Value: var programmes } => Results.Ok(programmes),
|
||||
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||
})
|
||||
.WithDescription("Get all programmes")
|
||||
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName())
|
||||
.MapToApiVersion(new ApiVersion(1))
|
||||
.Produces<Programme[]>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
@@ -14,7 +15,7 @@ public sealed class UpdateOrganisationEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request,
|
||||
GovernanceService service, CancellationToken cancellationToken = default) =>
|
||||
IGovernanceService service, CancellationToken cancellationToken = default) =>
|
||||
await service.UpdateOrganisationAsync(request, cancellationToken) switch
|
||||
{
|
||||
{ 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.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateAwardEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateBudgetEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateContractEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class ExecuteContractEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetAwardEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetAwardAsync(awardId, cancellationToken) switch
|
||||
{
|
||||
@@ -31,6 +31,7 @@ public class GetAwardEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.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.Services.Grants;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetAwardsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -27,6 +27,7 @@ public class GetAwardsEndpoint : IEndpoint
|
||||
.Produces<Award[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetBudgetEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetBudgetAsync(budgetId, cancellationToken) switch
|
||||
{
|
||||
@@ -31,6 +31,7 @@ public class GetBudgetEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Grants;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetBudgetsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -27,6 +27,7 @@ public class GetBudgetsEndpoint : IEndpoint
|
||||
.Produces<Budget[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Services.Grants;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetContractEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetContractAsync(contractId, cancellationToken) switch
|
||||
{
|
||||
@@ -31,6 +31,7 @@ public class GetContractEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Grants;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetContractsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -27,6 +27,7 @@ public class GetContractsEndpoint : IEndpoint
|
||||
.Produces<Contract[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateActivityEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ 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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class CreateProjectEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } result => Results.Ok(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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;
|
||||
@@ -14,7 +14,7 @@ public class GetActivitiesEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true, Value: var activities } => Results.Ok(activities),
|
||||
@@ -28,6 +28,7 @@ public class GetActivitiesEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetActivityEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true, Value: var activity } => Results.Ok(activity),
|
||||
@@ -30,6 +30,7 @@ public class GetActivityEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.WithTags(EndpointTags.Projects);
|
||||
.WithTags(EndpointTags.Projects)
|
||||
.CacheOutput();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Projects;
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetMilestoneEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetMilestoneAsync(milestoneId, cancellationToken) switch
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Projects;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,20 +14,20 @@ public class GetMilestonesEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetMilestonesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
||||
var r => Results.InternalServerError(r.Errors.ToProblems())
|
||||
})
|
||||
.RequireAuthorization()
|
||||
.WithDescription("Get a list of all milestones")
|
||||
.WithName(typeof(GetMilestonesEndpoint).ToEndpointName())
|
||||
.MapToApiVersion(new ApiVersion(1))
|
||||
.Produces<Milestone[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetProjectEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetProjectAsync(projectId, cancellationToken) switch
|
||||
{
|
||||
@@ -31,6 +31,7 @@ public class GetProjectEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetProjectIndicatorsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken) switch
|
||||
{
|
||||
@@ -30,6 +30,7 @@ public class GetProjectIndicatorsEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -15,7 +15,7 @@ public class GetProjectMilestonesEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
|
||||
@@ -30,6 +30,7 @@ public class GetProjectMilestonesEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -15,7 +15,7 @@ public class GetProjectRisksEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true, Value: var risks } => Results.Ok(risks),
|
||||
@@ -30,6 +30,7 @@ public class GetProjectRisksEndpoint : IEndpoint
|
||||
.Produces(StatusCodes.Status403Forbidden)
|
||||
.Produces(StatusCodes.Status401Unauthorized)
|
||||
.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.Services.Projects;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -14,7 +14,7 @@ public class GetProjectsEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
|
||||
{
|
||||
@@ -27,6 +27,7 @@ public class GetProjectsEndpoint : IEndpoint
|
||||
.Produces<Project[]>(StatusCodes.Status200OK)
|
||||
.Produces<Problems>(StatusCodes.Status500InternalServerError)
|
||||
.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.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
@@ -12,7 +13,7 @@ public sealed class UpdateMilestoneEndpoint : IEndpoint
|
||||
{
|
||||
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) =>
|
||||
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.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
@@ -14,7 +15,7 @@ public sealed class UpdateProjectEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ IsSuccess: true } => Results.Ok(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
using PostFundManagement.Application.Services.Projects;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
@@ -14,7 +15,7 @@ public class UpdateProjectStatusEndpoint : IEndpoint
|
||||
public void Map(IEndpointRouteBuilder builder)
|
||||
{
|
||||
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
|
||||
{
|
||||
{ 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>
|
||||
</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)-->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
|
||||
|
||||
@@ -42,6 +42,9 @@ builder.Services.AddHealthChecks()
|
||||
|
||||
IEnumerable<string> identityScopes = ["openid", "profile", "roles", "email", "address"];
|
||||
|
||||
builder.Services.AddValidation()
|
||||
.AddEndpointValidation();
|
||||
|
||||
builder.Services.AddOpenApi(options =>
|
||||
{
|
||||
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 schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
||||
@@ -100,6 +109,7 @@ app.UseHsts();
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
app.UseCors("AllowFrontend");
|
||||
app.UseOutputCache();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace PostFundManagement.Application.Abstractions;
|
||||
|
||||
public abstract class ServiceCacheBase
|
||||
{
|
||||
protected readonly DistributedCacheEntryOptions cacheOptions = new()
|
||||
{
|
||||
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)
|
||||
};
|
||||
}
|
||||
@@ -10,5 +10,7 @@ public interface ICommunicationsService
|
||||
|
||||
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 IServiceCollection AddEndpointValidation(this IServiceCollection services) => services.AddValidation();
|
||||
|
||||
public static IServiceCollection AddEndpointAndServiceCaching(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddStackExchangeRedisOutputCache(options =>
|
||||
|
||||
@@ -1,56 +1,12 @@
|
||||
using PostFundManagement.Application.Abstractions;
|
||||
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;
|
||||
namespace PostFundManagement.Application.Extensions;
|
||||
|
||||
public static class General
|
||||
{
|
||||
public static IServiceCollection AddRegisterServicesWithCaching(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<ICommunicationsService, CommunicationsService>();
|
||||
services.Decorate<ICommunicationsService, CommunicationsCacheService>();
|
||||
public static string Clean(this string input) => Patterns.SpaceRegex().Replace(input, string.Empty);
|
||||
|
||||
services.AddScoped<IEvidenceService, EvidenceService>();
|
||||
services.Decorate<IEvidenceService, EvidenceCacheService>();
|
||||
public static string ToJson<TValue>(this TValue obj) where TValue : class => JsonSerializer.Serialize(obj);
|
||||
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
services.Decorate<IUserService, UserCacheService>();
|
||||
|
||||
services.AddScoped<IIdentityService, IdentityService>();
|
||||
services.Decorate<IIdentityService, IdentityCacheService>();
|
||||
|
||||
services.AddStackExchangeRedisCache(options =>
|
||||
{
|
||||
options.Configuration = configuration.GetConnectionString("Redis");
|
||||
options.ConfigurationOptions!.ClientName = "PFM-API";
|
||||
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 TValue FromJson<TValue>(this string json) where TValue : class => JsonSerializer.Deserialize<TValue>(json)!;
|
||||
|
||||
public static Guid GetUserId(this HttpContext context) => (Guid)context.Items[Domain.Extensions.Constants.UserIdItemKey]!;
|
||||
|
||||
@@ -78,6 +34,6 @@ public static class General
|
||||
{
|
||||
Detail = string.Join(',', errors.Select(e => e.Message)),
|
||||
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>
|
||||
</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>
|
||||
<ProjectReference Include="..\PostFundManagement.Domain\PostFundManagement.Domain.csproj" />
|
||||
<ProjectReference Include="..\PostFundManagement.Infrastructure\PostFundManagement.Infrastructure.csproj" />
|
||||
@@ -17,6 +25,7 @@
|
||||
<!-- API Documentation -->
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
|
||||
<Using Include="Scalar.AspNetCore" />
|
||||
@@ -26,6 +35,9 @@
|
||||
|
||||
<!-- Shared Usings -->
|
||||
<ItemGroup>
|
||||
<Using Include="Humanizer" />
|
||||
<Using Include="System.ComponentModel.DataAnnotations" />
|
||||
<Using Include="Microsoft.Extensions.Caching.Distributed" />
|
||||
<Using Include="Microsoft.Extensions.Caching.Hybrid" />
|
||||
<Using Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<Using Include="Asp.Versioning" />
|
||||
|
||||
+60
-7
@@ -1,21 +1,74 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
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.Communications;
|
||||
|
||||
public sealed class CommunicationsCacheService(CommunicationsService service, HybridCache cache) : 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 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) =>
|
||||
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) =>
|
||||
await service.SendInviteAsync(request, userId, cancellationToken);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Domain;
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
@@ -39,9 +38,8 @@ public sealed class CommunicationsService(IDbContextFactory<ApplicationDbContext
|
||||
CreatedAt = DateTime.UtcNow
|
||||
});
|
||||
|
||||
return await context.SaveChangesAsync(cancellationToken) > 0
|
||||
? Result.Ok(invite.Map())
|
||||
: Result.Fail<Invite>(CreateError.New);
|
||||
return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New)
|
||||
.ToResult(invite.Map());
|
||||
}));
|
||||
|
||||
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());
|
||||
}));
|
||||
|
||||
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 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
|
||||
? Result.Ok(invite.Map())
|
||||
: Result.Fail<Invite>(NotFoundError.New);
|
||||
return Result.FailIf(invite is null, NotFoundError.New)
|
||||
.ToResult(invite!.Map());
|
||||
}));
|
||||
|
||||
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 static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
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,11 +1,13 @@
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
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.Evidence;
|
||||
|
||||
public sealed class EvidenceCacheService(EvidenceService service, HybridCache cache) : 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) =>
|
||||
await service.CreateEvidenceAsync(userId, form, file, cancellationToken);
|
||||
@@ -18,33 +20,129 @@ public sealed class EvidenceCacheService(EvidenceService service, HybridCache ca
|
||||
|
||||
public async ValueTask<Result<Domain.Models.Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
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) =>
|
||||
await service.RecordIndicatorActualAmountAsync(indicatorIdHash, amount, userId, cancellationToken);
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
|
||||
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) =>
|
||||
@@ -66,7 +66,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
.SetProperty(f => f.ActualAmount, amount)
|
||||
.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) =>
|
||||
@@ -102,6 +102,9 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
{
|
||||
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))
|
||||
return Result.Fail(NotFoundError.New);
|
||||
|
||||
@@ -116,7 +119,8 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
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) =>
|
||||
@@ -139,7 +143,7 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
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) =>
|
||||
@@ -155,9 +159,8 @@ public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> cont
|
||||
|
||||
var indicator = await context.Indicators.AsNoTracking().FirstOrDefaultAsync(i => i.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return indicator is not null
|
||||
? Result.Ok(indicator.Map())
|
||||
: Result.Fail(NotFoundError.New);
|
||||
return Result.FailIf(indicator is null, NotFoundError.New)
|
||||
.ToResult(indicator!.Map());
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
return evidence is not null
|
||||
? Result.Ok(evidence.Map())
|
||||
: Result.Fail<Domain.Models.Evidence>(NotFoundError.New);
|
||||
return Result.FailIf(evidence is null, NotFoundError.New)
|
||||
.ToResult(evidence!.Map());
|
||||
}));
|
||||
|
||||
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.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 static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
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.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
@@ -8,7 +8,7 @@ using PostFundManagement.Infrastructure.Database;
|
||||
|
||||
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) =>
|
||||
await Result.Try((Func<ValueTask<Result<Risk[]>>>)(async () =>
|
||||
@@ -57,7 +57,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
||||
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) =>
|
||||
@@ -78,7 +78,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
||||
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) =>
|
||||
@@ -98,7 +98,7 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
||||
.SetProperty(f => f.UpdatedBy, userId)
|
||||
.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) =>
|
||||
@@ -115,12 +115,11 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
||||
var disbursement = await context.Disbursements.AsNoTracking()
|
||||
.FirstOrDefaultAsync(d => d.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return disbursement is not null
|
||||
? Result.Ok(disbursement.Map())
|
||||
: Result.Fail<Disbursement>(NotFoundError.New);
|
||||
return Result.FailIf(disbursement is null, NotFoundError.New)
|
||||
.ToResult(disbursement!.Map());
|
||||
}));
|
||||
|
||||
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 () =>
|
||||
{
|
||||
var decodeResult = hashService.DecodeLongIdHash(riskIdHash);
|
||||
@@ -134,12 +133,11 @@ public sealed class ExecutionService(IDbContextFactory<ApplicationDbContext> con
|
||||
var risk = await context.Risks.AsNoTracking()
|
||||
.FirstOrDefaultAsync(r => r.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return risk is not null
|
||||
? Result.Ok(risk.Map())
|
||||
: Result.Fail<Risk>(NotFoundError.New);
|
||||
return Result.FailIf(risk is null, NotFoundError.New)
|
||||
.ToResult(risk!.Map());
|
||||
}));
|
||||
|
||||
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 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.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 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.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 static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
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.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
using PostFundManagement.Infrastructure.Database;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
|
||||
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 () =>
|
||||
{
|
||||
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().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());
|
||||
}));
|
||||
|
||||
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 () =>
|
||||
{
|
||||
var decodeResult = hashService.DecodeLongIdHash(organisationIdHash);
|
||||
|
||||
if (decodeResult.IsFailed)
|
||||
return Result.Fail(IdDecodeError.Create()
|
||||
.CausedBy(decodeResult.Errors));
|
||||
|
||||
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());
|
||||
}));
|
||||
@@ -58,17 +77,25 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
||||
var organisation = await context.Organisations.AsNoTracking()
|
||||
.FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return organisation is not null
|
||||
? Result.Ok(organisation.Map())
|
||||
: Result.Fail(NotFoundError.New);
|
||||
return Result.FailIf(organisation is null, NotFoundError.New)
|
||||
.ToResult(organisation!.Map());
|
||||
}));
|
||||
|
||||
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 () =>
|
||||
{
|
||||
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 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());
|
||||
}));
|
||||
@@ -104,21 +131,23 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
||||
{
|
||||
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"));
|
||||
|
||||
context.Organisations.Add(new Domain.Entities.Organisation
|
||||
{
|
||||
Name = request.Name,
|
||||
RegistrationNo = request.RegistrationNo,
|
||||
Email = request.Email,
|
||||
Name = request.Name.Humanize(LetterCasing.Title).Trim(),
|
||||
RegistrationNo = registrationNo,
|
||||
Email = request.Email.ToLower(CultureInfo.InvariantCulture).Clean(),
|
||||
Type = request.Type,
|
||||
Status = ActivityStatus.Active,
|
||||
CreatedBy = userId,
|
||||
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) =>
|
||||
@@ -129,11 +158,95 @@ public sealed class GovernanceService(IDbContextFactory<ApplicationDbContext> co
|
||||
var changedTotal = await context.Organisations
|
||||
.Where(f => f.Id == request.OrganisationId)
|
||||
.ExecuteUpdateAsync(f => f
|
||||
.SetProperty(u => u.Name, request.Name)
|
||||
.SetProperty(u => u.RegistrationNo, request.RegistrationNo)
|
||||
.SetProperty(u => u.Email, request.Email)
|
||||
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||
.SetProperty(u => u.RegistrationNo, request.RegistrationNo.Clean())
|
||||
.SetProperty(u => u.Email, request.Email.ToLower(CultureInfo.InvariantCulture).Clean())
|
||||
.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 static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
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.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
@@ -8,7 +8,7 @@ using PostFundManagement.Infrastructure.Database;
|
||||
|
||||
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) =>
|
||||
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()
|
||||
.FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return contract is not null
|
||||
? Result.Ok(contract.Map())
|
||||
: Result.Fail<Contract>(NotFoundError.New);
|
||||
return Result.FailIf(contract is null, NotFoundError.New)
|
||||
.ToResult(contract!.Map());
|
||||
}));
|
||||
|
||||
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()
|
||||
.FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return budget is not null
|
||||
? Result.Ok(budget.Map())
|
||||
: Result.Fail<Budget>(NotFoundError.New);
|
||||
return Result.FailIf(budget is null, NotFoundError.New)
|
||||
.ToResult(budget!.Map());
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
return award is not null
|
||||
? Result.Ok(award.Map())
|
||||
: Result.Fail<Award>(NotFoundError.New);
|
||||
return Result.FailIf(award is null, NotFoundError.New)
|
||||
.ToResult(award!.Map());
|
||||
}));
|
||||
|
||||
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.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) =>
|
||||
@@ -148,7 +145,7 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
||||
|
||||
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) =>
|
||||
@@ -173,7 +170,7 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
||||
|
||||
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) =>
|
||||
@@ -202,6 +199,6 @@ public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contex
|
||||
|
||||
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.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Domain.Extensions;
|
||||
using PostFundManagement.Domain.Models;
|
||||
@@ -8,32 +8,24 @@ using PostFundManagement.Infrastructure.Database;
|
||||
|
||||
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) =>
|
||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||
{
|
||||
await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken);
|
||||
|
||||
if (project == null)
|
||||
if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken))
|
||||
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
|
||||
.Where(p => p.Id != request.ProjectId)
|
||||
.Where(f => f.Id == request.ProjectId)
|
||||
.ExecuteUpdateAsync(f => f
|
||||
.SetProperty(p => p.Status, request.Status)
|
||||
.SetProperty(p => p.UpdatedBy, userId)
|
||||
.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) =>
|
||||
@@ -63,9 +55,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
var project = await context.Projects.AsNoTracking()
|
||||
.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return project is not null
|
||||
? Result.Ok(project.Map())
|
||||
: Result.Fail<Project>(NotFoundError.Create());
|
||||
return Result.FailIf(project is null, NotFoundError.New)
|
||||
.ToResult(project!.Map());
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
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"));
|
||||
|
||||
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
||||
return Result.Fail(NotFoundError.New);
|
||||
|
||||
var description = request.Description.Humanize(LetterCasing.Sentence).Trim();
|
||||
|
||||
context.Projects.Add(new Domain.Entities.Project
|
||||
{
|
||||
ProgrammeId = request.ProgrammeId,
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
Name = name,
|
||||
Description = description,
|
||||
Sector = request.Sector,
|
||||
ProjectType = request.ProjectType,
|
||||
Status = ApprovalStatus.Pending,
|
||||
@@ -109,7 +104,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
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) =>
|
||||
@@ -128,9 +124,9 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
{
|
||||
ProjectId = request.ProjectId,
|
||||
MilestoneId = request.MilestoneId,
|
||||
Category = request.Category,
|
||||
Category = request.Category.Humanize(LetterCasing.Title).Trim(),
|
||||
Amount = request.Amount,
|
||||
Title = request.Title,
|
||||
Title = request.Title.Humanize(LetterCasing.Title).Trim(),
|
||||
Description = request.Description,
|
||||
PerformedAt = request.PerformedAt,
|
||||
Status = ApprovalStatus.Pending,
|
||||
@@ -138,7 +134,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
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) =>
|
||||
@@ -179,7 +176,7 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
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 () =>
|
||||
{
|
||||
var decodeResult = hashService.DecodeLongIdHash(milestoneIdHash);
|
||||
@@ -192,9 +189,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
var milestone = await context.Milestones.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return milestone is not null
|
||||
? Result.Ok(milestone.Map())
|
||||
: Result.Fail<Milestone>(NotFoundError.Create());
|
||||
return Result.FailIf(milestone is null, NotFoundError.New)
|
||||
.ToResult(milestone!.Map());
|
||||
}));
|
||||
|
||||
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());
|
||||
}));
|
||||
|
||||
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 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)
|
||||
.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.Priority, request.Priority)
|
||||
.SetProperty(u => u.Status, request.Status)
|
||||
.SetProperty(u => u.UpdatedBy, userId)
|
||||
.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 using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
||||
|
||||
var changedTotal = await context.Projects.Where(p => p.Id == request.ProjectId)
|
||||
.ExecuteUpdateAsync(f => f
|
||||
.SetProperty(u => u.Name, request.Name)
|
||||
.SetProperty(u => u.Description, request.Description)
|
||||
.SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim())
|
||||
.SetProperty(u => u.Description, request.Description.Humanize(LetterCasing.Sentence).Trim())
|
||||
.SetProperty(u => u.StartedAt, request.StartedAt)
|
||||
.SetProperty(u => u.EndedAt, request.EndedAt)
|
||||
.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.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 () =>
|
||||
{
|
||||
var decodeResult = hashService.DecodeLongIdHash(activityIdHash);
|
||||
@@ -261,9 +260,8 @@ public sealed class ProjectService(IDbContextFactory<ApplicationDbContext> conte
|
||||
var activity = await context.Activities.AsNoTracking()
|
||||
.FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
||||
|
||||
return activity is not null
|
||||
? Result.Ok(activity.Map())
|
||||
: Result.Fail<Domain.Models.Activity>(NotFoundError.New);
|
||||
return Result.FailIf(activity is null, NotFoundError.New)
|
||||
.ToResult(activity!.Map());
|
||||
}));
|
||||
|
||||
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());
|
||||
}));
|
||||
|
||||
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 static PostFundManagement.Domain.Extensions.Constants;
|
||||
|
||||
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);
|
||||
|
||||
return retrieval.Success && !string.IsNullOrWhiteSpace(retrieval.Value)
|
||||
? Result.Ok(retrieval.Value)
|
||||
: Result.Fail(NotFoundError.Create($"Could not find object by key {key}"));
|
||||
return Result.FailIf(!retrieval.Success && string.IsNullOrWhiteSpace(retrieval.Value),
|
||||
NotFoundError.Create($"Could not find object by key {key}"))
|
||||
.ToResult(retrieval.Value!);
|
||||
}));
|
||||
|
||||
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);
|
||||
|
||||
return retrieval.Success && retrieval.Value is not null
|
||||
? Result.Ok(retrieval.Value)
|
||||
: Result.Fail(NotFoundError.Create($"Could not find object by key {key}"));
|
||||
return Result.FailIf(!retrieval.Success && retrieval.Value is null,
|
||||
NotFoundError.Create($"Could not find object by key {key}"))
|
||||
.ToResult(retrieval.Value!);
|
||||
}));
|
||||
}
|
||||
@@ -1,24 +1,16 @@
|
||||
using PostFundManagement.Domain.Abstractions;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
using PostFundManagement.Application.Extensions;
|
||||
|
||||
namespace PostFundManagement.Application.Services.Shared;
|
||||
|
||||
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) =>
|
||||
!string.IsNullOrWhiteSpace(value) && Md5Regex.IsMatch(value);
|
||||
!string.IsNullOrWhiteSpace(value) && Patterns.Md5Regex.IsMatch(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) =>
|
||||
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());
|
||||
}
|
||||
|
||||
public Result<string> HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input)
|
||||
? Result.Fail<string>(ValidationError.Create("Input must be a valid hexadecimal string."))
|
||||
: Result.Ok(hasher.EncodeHex(input));
|
||||
public Result<string> HashEncodeHex(string input) =>
|
||||
Result.FailIf(string.IsNullOrWhiteSpace(input) || !Patterns.HexHashRegex.IsMatch(input),
|
||||
ValidationError.Create("Input must be a valid hexadecimal string."))
|
||||
.ToResult(hasher.EncodeHex(input));
|
||||
|
||||
public Result<string> HashEncodeIntId(int id) => id < 0
|
||||
? Result.Fail<string>(ValidationError.Create("Id cannot be negative."))
|
||||
: Result.Ok(hasher.Encode(id));
|
||||
public Result<string> HashEncodeIntId(int id) =>
|
||||
Result.FailIf(id < 0, ValidationError.Create("Id cannot be negative."))
|
||||
.ToResult(hasher.Encode(id));
|
||||
|
||||
public Result<string> HashEncodeLongId(long id) => id < 0
|
||||
? Result.Fail<string>(ValidationError.Create("Id cannot be negative."))
|
||||
: Result.Ok(hasher.EncodeLong(id));
|
||||
public Result<string> HashEncodeLongId(long id) =>
|
||||
Result.FailIf(id < 0, ValidationError.Create("Id cannot be negative."))
|
||||
.ToResult(hasher.EncodeLong(id));
|
||||
|
||||
public Result<int> DecodeIntIdHash(string hash)
|
||||
{
|
||||
@@ -58,9 +51,8 @@ public sealed partial class HashService(IHashids hasher) : IService
|
||||
|
||||
int[] decoded = hasher.Decode(hash);
|
||||
|
||||
return decoded.Length == 1
|
||||
? Result.Ok(decoded[0]) :
|
||||
Result.Fail<int>(ValidationError.Create("Invalid or modified Int hash token."));
|
||||
return Result.FailIf(decoded.Length <= 0, ValidationError.Create("Invalid or modified Int hash token."))
|
||||
.ToResult(decoded[0]);
|
||||
}
|
||||
|
||||
public Result<long> DecodeLongIdHash(string hash)
|
||||
@@ -70,8 +62,8 @@ public sealed partial class HashService(IHashids hasher) : IService
|
||||
|
||||
long[] decoded = hasher.DecodeLong(hash);
|
||||
|
||||
return decoded.Length == 1 ? Result.Ok(decoded[0]) :
|
||||
Result.Fail<long>(ValidationError.Create("Invalid or modified Long hash token."));
|
||||
return Result.FailIf(decoded.Length <= 0, ValidationError.Create("Invalid or modified Long hash token."))
|
||||
.ToResult(decoded[0]);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
return string.IsNullOrEmpty(decoded)
|
||||
? Result.Fail<string>(ValidationError.Create("Invalid or corrupted hex hash."))
|
||||
: Result.Ok(decoded);
|
||||
return Result.FailIf(string.IsNullOrEmpty(decoded), ValidationError.Create("Invalid or corrupted hex hash."))
|
||||
.ToResult(decoded);
|
||||
}));
|
||||
}
|
||||
@@ -1,31 +1,10 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using PostFundManagement.Application.Abstractions;
|
||||
using PostFundManagement.Application.Abstractions.Services;
|
||||
using PostFundManagement.Domain.Errors;
|
||||
|
||||
namespace PostFundManagement.Application.Services.Shared;
|
||||
|
||||
public sealed class IdentityCacheService(IdentityService service, IDistributedCache cache) : IIdentityService
|
||||
public sealed class IdentityCacheService(IdentityService service) : ServiceCacheBase, IIdentityService
|
||||
{
|
||||
public async ValueTask<Result> UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) =>
|
||||
await Result.Try((Func<ValueTask<Result>>)(async () =>
|
||||
{
|
||||
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", cancellationToken);
|
||||
|
||||
return Result.Ok();
|
||||
}
|
||||
|
||||
return cached.Equals("true", StringComparison.OrdinalIgnoreCase)
|
||||
? Result.Ok()
|
||||
: Result.Fail(NotFoundError.New);
|
||||
}));
|
||||
await service.UserIsAuthorisedAsync(userId, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@ public sealed class IdentityService(IDbContextFactory<ApplicationDbContext> cont
|
||||
var userExistsAndActive = await context.Users
|
||||
.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);
|
||||
|
||||
return !string.IsNullOrWhiteSpace(tokenResponse?.AccessToken)
|
||||
? Result.Ok(tokenResponse)
|
||||
: Result.Fail<Domain.Security.Models.TokenResponse>(NotFoundError.Create("Authentication succeeded, but no access token was found in the response payload."));
|
||||
return Result.FailIf(string.IsNullOrWhiteSpace(tokenResponse?.AccessToken),
|
||||
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 () =>
|
||||
{
|
||||
var errorResult = JsonSerializer.Deserialize<TokenErrorResponse>(contentRaw);
|
||||
|
||||
return Result.FailIf(errorResult != null,
|
||||
return Result.OkIf(errorResult is not null,
|
||||
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,12 +1,13 @@
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
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.Users;
|
||||
|
||||
public sealed class UserCacheService(UserService service, IDistributedCache cache) : IUserService
|
||||
public sealed class UserCacheService(UserService service, IDistributedCache cache) : ServiceCacheBase, IUserService
|
||||
{
|
||||
public async ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken) =>
|
||||
await Result.Try((Func<ValueTask<Result<User>>>)(async () =>
|
||||
@@ -21,20 +22,19 @@ public sealed class UserCacheService(UserService service, IDistributedCache cach
|
||||
|
||||
if (userResult.IsFailed) return userResult;
|
||||
|
||||
await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(userResult.Value));
|
||||
await cache.SetStringAsync(cacheKey, userResult.Value.ToJson(), cacheOptions, cancellationToken);
|
||||
|
||||
return userResult;
|
||||
}
|
||||
|
||||
var restored = JsonSerializer.Deserialize<User>(cached)!;
|
||||
|
||||
return Result.Ok(restored);
|
||||
return Result.Ok(cached.FromJson<User>());
|
||||
}));
|
||||
|
||||
public async ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) =>
|
||||
await Result.Try((Func<ValueTask<Result<User[]>>>)(async () =>
|
||||
{
|
||||
var cacheKey = HashService.ToMd5Hash($"getusers-{pagination.Page}-{pagination.PageSize}-{request.Email}-{request.Status}").Value;
|
||||
var cacheKey = HashService.ToMd5Hash($"getusers-{pagination.Page}-{pagination.PageSize}-" +
|
||||
$"{request.Email}-{request.Status}-{request.SortBy}-{request.SortDescending}").Value;
|
||||
|
||||
var cached = await cache.GetStringAsync(cacheKey, cancellationToken);
|
||||
|
||||
@@ -44,14 +44,13 @@ public sealed class UserCacheService(UserService service, IDistributedCache cach
|
||||
|
||||
if (usersResult.IsFailed) return usersResult;
|
||||
|
||||
await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(usersResult.Value));
|
||||
await cache.SetStringAsync(cacheKey, usersResult.Value.ToJson(),
|
||||
cacheOptions, cancellationToken);
|
||||
|
||||
return usersResult;
|
||||
}
|
||||
|
||||
var restored = JsonSerializer.Deserialize<User[]>(cached)!;
|
||||
|
||||
return Result.Ok(restored);
|
||||
return Result.Ok(cached.FromJson<User[]>());
|
||||
}));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) =>
|
||||
@@ -74,7 +74,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
||||
.Where(f => f.Id == userId)
|
||||
.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) =>
|
||||
@@ -85,8 +85,7 @@ public sealed class UserService(IDbContextFactory<ApplicationDbContext> contextF
|
||||
var user = await context.Users.AsNoTracking()
|
||||
.FirstOrDefaultAsync(f => f.Id == userId, cancellationToken);
|
||||
|
||||
return user is not null
|
||||
? Result.Ok(user.Map())
|
||||
: Result.Fail(NotFoundError.New);
|
||||
return Result.FailIf(user is null, NotFoundError.New)
|
||||
.ToResult(user!.Map());
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -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;"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user