47 Commits
Author SHA1 Message Date
hermes ea0e4eb018 Added image name to compose file for console 2026-09-11 16:23:50 +02:00
hermes 9ebbafb840 Added more cors origins 2026-09-11 16:16:41 +02:00
hermes f3863c785d Refactored console dockerfile 2026-09-11 16:04:54 +02:00
hermes 9874eac65d Fixed dockerfile paths 2026-09-11 15:47:02 +02:00
hermes 2b8edd05e3 Supplied defaults to compose env variables 2026-09-11 15:33:15 +02:00
hermes edbae6defb Fixed compose syntax error 2026-09-11 15:30:20 +02:00
hermes b3e884336d Refactored compose file to include the ui in deployment 2026-09-11 15:28:58 +02:00
hermes 1b0ceb8ea9 Updated Console compose file to use variables 2026-09-11 15:19:04 +02:00
hermes d779e4152e Added specific origin 2026-09-11 14:59:11 +02:00
hermes 9e611cb79c Added cors after routes 2026-09-11 14:54:25 +02:00
hermes b99ebf657c Added cors to API 2026-09-11 14:34:17 +02:00
hermes 99ab5b2ce6 Added PFM Console UI project 2026-09-11 12:13:25 +02:00
hermes a15b25248d Improved results handling in service layer 2026-09-11 10:32:51 +02:00
hermes 66f720a20b Stable run with validations 2026-09-06 10:48:09 +02:00
hermes 81c111c3c3 Added tooling 2026-09-05 18:24:21 +02:00
hermes 415aa30cf7 Fixed endpointname uniqueness 2026-09-05 13:07:11 +02:00
hermes 72ea20a2de Fixed endpoint uniqueness 2026-09-05 12:57:47 +02:00
hermes 15661ce8cb Covered gvernance endpoints
Completed communication inte endpoints
2026-09-05 12:49:27 +02:00
hermes e274c33592 Migrated portfolio to be linked to the organisation 2026-09-05 10:57:10 +02:00
hermes a218b19875 Added program control endpoints 2026-09-05 10:27:52 +02:00
hermes 0a1378fe25 Implemented caching on all get endpoints and service layer 2026-09-05 08:26:15 +02:00
hermes 6719699689 Improved getusers cache key 2026-09-04 06:07:59 +02:00
hermes 8ea014e4fb Refactored cache ttl handling 2026-09-04 05:53:36 +02:00
hermes 9728459223 Removed client name definition on redis config 2026-09-04 05:46:10 +02:00
hermes a73f53a0d9 Additional redis configs 2026-09-04 05:37:59 +02:00
hermes 9f76b203e3 Added service level caching to IdentityService, Communication and Evidence 2026-09-03 16:37:25 +02:00
hermes da4499ce41 Implemented distributed caching 2026-09-03 11:09:05 +02:00
hermes d55fb59d8d Updated nuget packages 2026-09-02 16:13:07 +02:00
hermes a04e6f6f50 Stable user endpoints
Removed security deadlock
2026-09-02 16:11:10 +02:00
hermes 669945ad13 Updated configurations 2026-09-02 09:45:00 +02:00
hermes cba63e2609 Removed node tooling install 2026-09-02 09:29:27 +02:00
hermes 14b1f3a397 Chnaged app port 2026-09-02 09:26:57 +02:00
hermes adc413d12b Added docker files 2026-09-02 09:25:19 +02:00
hermes b8aa5651fa Added missing endpoints 2026-09-01 12:29:00 +02:00
hermes 2df5b5c2ed Refactored endpoints to be more functional and more expressive 2026-09-01 11:23:25 +02:00
hermes 346cb8da5f Expanded evidence and project endpoints coverage 2026-08-30 17:46:46 +02:00
hermes 9d90f9120b Implemented GetProjectIndicatorsEndpoint 2026-08-30 16:50:40 +02:00
hermes 5caa806600 Implemented GetEvidenceEndpoint 2026-08-30 16:27:13 +02:00
hermes 2aed75fd4e Implemented GetMilestoneEndpoint 2026-08-30 16:19:59 +02:00
hermes db8d336c18 Implemented GetIndicatorByIdEndpoint 2026-08-30 16:08:22 +02:00
hermes 1d406a9184 Implemented GetApplicantInviteEndpoint 2026-08-30 15:57:50 +02:00
hermes 841763441d Implemented GetUserEndpoint 2026-08-30 15:46:46 +02:00
hermes 6c7db78e17 Implemented UpdateUserStatusEndpoint 2026-08-30 15:38:34 +02:00
hermes c6d1d862ff Consolidate SyncUserEvent endpoints 2026-08-30 15:19:56 +02:00
hermes 4827cb5795 Reogranised API and Security assets 2026-08-30 15:08:58 +02:00
hermes c204cbb1c2 Moved service implemntations into Application.Services folder and namespace 2026-08-30 14:53:16 +02:00
hermes 0f7f875abe Moved DI extensions to Appl;ication layer 2026-08-30 14:49:10 +02:00
242 changed files with 26006 additions and 2462 deletions
+3
View File
@@ -43,6 +43,9 @@ dotnet_diagnostic.MA0004.severity = none
[**/*{Enums,Records}.cs] [**/*{Enums,Records}.cs]
dotnet_diagnostic.MA0048.severity = none dotnet_diagnostic.MA0048.severity = none
[*.{cs,vb}]
dotnet_diagnostic.IDE0039.severity = none
#### Core EditorConfig Options #### #### Core EditorConfig Options ####
# Indentation and spacing # Indentation and spacing
+28
View File
@@ -0,0 +1,28 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM node:22-bookworm-slim AS node
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG TARGETARCH
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 \
NUGET_XMLDOC_MODE=skip
WORKDIR /app_source
COPY . .
RUN dotnet restore "PostFundManagement.slnx"
WORKDIR "/app_source/PostFundManagement.Api"
RUN dotnet build "PostFundManagement.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "PostFundManagement.Api.csproj" -c Release --no-restore -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
ENTRYPOINT ["dotnet", "PostFundManagement.Api.dll"]
@@ -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 GetApplicantInviteEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/communications/invites/applicant/{applicantUserId:Guid}", async (Guid applicantUserId, ICommunicationsService service,
HttpContext context, CancellationToken cancellationToken = default) =>
await service.GetApplicantInviteAsync(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")
.WithName(typeof(GetApplicantInviteEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Invite>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Communications)
.CacheOutput();
}
}
@@ -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();
}
}
@@ -1,35 +1,34 @@
using PostFundManagement.Application.Communications; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using static PostFundManagement.Domain.Extensions.General; using PostFundManagement.Domain.Security;
using static PostFundManagement.Application.Extensions.General;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class GetInvitesEndpoint : IEndpoint public sealed class GetInvitesEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/communications/invites/{page:int}/{pageSize:int}", async (CommunicationsService service, builder.MapGet("api/communications/invites/{page:int}/{pageSize:int}", async (ICommunicationsService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetInvitesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetInvitesAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var invites } => Results.Ok(invites),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a list of sent invitations")
}) .WithName(typeof(GetInvitesEndpoint).ToEndpointName())
.WithDescription("Get a list of sent invitations") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetInvitesEndpoint).ToEndpointName()) .Produces<Invite[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Invite[]>(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Communications)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Communications)
.CacheOutput();
} }
} }
@@ -1,31 +1,35 @@
using PostFundManagement.Application.Communications; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Communications; namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class SendInviteEndpoint : IEndpoint public sealed class SendInviteEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/communications/invite", async (InviteRequest request, builder.MapPost("api/communications/invite", async (InviteRequest request,
HttpContext context, CommunicationsService service, CancellationToken cancellationToken = default) => HttpContext context, ICommunicationsService service, CancellationToken cancellationToken = default) =>
{ await service.SendInviteAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.SendInviteAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true, Value: var invite } => Results.Ok(invite),
return result.IsSuccess var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
? Results.Ok(result.Value) var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Trigger an invite to an organization or candidate (specifying NotificationPlatform)")
.WithDescription("Trigger an invite to an organization or candidate (specifying NotificationPlatform)") .WithName(typeof(SendInviteEndpoint).ToEndpointName())
.WithName(typeof(SendInviteEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces<Invite>(StatusCodes.Status200OK)
.Produces<Domain.Entities.Invite>(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Communications); .WithTags(EndpointTags.Communications);
} }
} }
@@ -1,30 +0,0 @@
using PostFundManagement.Application.Communications.Events;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
namespace PostFundManagement.Api.Endpoints.Communications;
[ApiVersionTarget(1)]
public class SyncIdentityUserEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/integration/sync", async (IJobOrchestrator jobOrchestrator,
CancellationToken cancellationToken = default) =>
{
var syncEvent = new SyncUserEvent();
await jobOrchestrator.SendAsync(syncEvent, cancellationToken);
return Results.Ok();
})
.WithDescription("Trigger integration and data synchronization in the background")
.WithName(typeof(SyncIdentityUserEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Communications);
}
}
@@ -1,17 +1,19 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class UploadEvidenceFilesEndpoint : IEndpoint public class CreateEvidenceEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/evidence/files", async (HttpRequest request, EvidenceService service, builder.MapPut("api/evidence/file", async (HttpRequest request, IEvidenceService service,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
{ {
var userId = request.HttpContext.GetUserId(); var userId = request.HttpContext.GetUserId();
@@ -25,19 +27,21 @@ public class UploadEvidenceFilesEndpoint : IEndpoint
if (file == null || file.Length == 0) if (file == null || file.Length == 0)
return Results.BadRequest("No file uploaded or file is empty."); return Results.BadRequest("No file uploaded or file is empty.");
var result = await service.UploadEvidenceFileAsync(userId, form, file, cancellationToken); return await service.CreateEvidenceAsync(userId, form, file, cancellationToken) switch
{
return result.IsSuccess { IsSuccess: true } => Results.Ok(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
};
}) })
.RequireAuthorization() .RequireAuthorization()
.DisableAntiforgery() .DisableAntiforgery()
.WithDescription("Upload supporting evidence file to S3 and register metadata (M14)") .WithDescription("Upload supporting evidence file to S3 and register metadata (M14)")
.WithName(typeof(UploadEvidenceFilesEndpoint).ToEndpointName()) .WithName(typeof(CreateEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Evidence); .WithTags(EndpointTags.Evidence);
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -12,20 +15,22 @@ public class CreateIndicatorEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/evidence/indicators", async (CreateIndicatorRequest request, HttpContext context, builder.MapPost("api/evidence/indicators", async (CreateIndicatorRequest request, HttpContext context,
EvidenceService service, CancellationToken cancellationToken = default) => IEvidenceService service, CancellationToken cancellationToken = default) =>
{ await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Define indicators and baselines/targets (M08)") .WithDescription("Define indicators and baselines/targets (M08)")
.WithName(typeof(CreateIndicatorEndpoint).ToEndpointName()) .WithName(typeof(CreateIndicatorEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Evidence); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -12,20 +15,21 @@ public class CreateMilestoneEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/evidence/milestones", async (CreateMilestoneRequest request, HttpContext context, builder.MapPost("api/evidence/milestones", async (CreateMilestoneRequest request, HttpContext context,
EvidenceService service, CancellationToken cancellationToken = default) => IEvidenceService service, CancellationToken cancellationToken = default) =>
{ await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Define contractual milestones (M07)") .WithDescription("Define contractual milestones (M07)")
.WithName(typeof(CreateMilestoneRequest).ToEndpointName()) .WithName(typeof(CreateMilestoneRequest).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status400BadRequest)
.WithTags(EndpointTags.Evidence); .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence);
} }
} }
@@ -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.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence;
[ApiVersionTarget(1)]
public sealed class GetEvidenceEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/{evidenceId}", async (string evidenceId, IEvidenceService service,
CancellationToken cancellationToken = default) =>
await service.GetEvidenceAsync(evidenceId, cancellationToken) switch
{
{ IsSuccess: true, Value: var evidence } => Results.Ok(evidence),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a specific evidence record and assiciated file URLs")
.WithName(typeof(GetEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Evidence>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence)
.CacheOutput();
}
}
@@ -1,9 +1,10 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -12,22 +13,20 @@ public class GetEvidenceFilesEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/evidence/files/{page:int}/{pageSize:int}", async (EvidenceService service, builder.MapGet("api/evidence/files/{page:int}/{pageSize:int}", async (IEvidenceService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetEvidenceFilesAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetEvidenceFilesAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var evidences} => Results.Ok(evidences),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a list of all uploaded evidence files metadata")
}) .WithName(typeof(GetEvidenceFilesEndpoint).ToEndpointName())
.WithDescription("Get a list of all uploaded evidence files metadata") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetEvidenceFilesEndpoint).ToEndpointName()) .Produces<Domain.Models.Evidence[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Domain.Models.Evidence[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Evidence)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Evidence)
.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.Evidence;
[ApiVersionTarget(1)]
public sealed class GetIndicatorEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/indicators/{indicatorId}", async (string indicatorId, IEvidenceService service,
CancellationToken cancellationToken = default) =>
await service.GetIndicatorAsync(indicatorId, cancellationToken) switch
{
{ IsSuccess: true, Value: var indicator } => Results.Ok(indicator),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a specific indicator.")
.WithName(typeof(GetIndicatorEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Indicator>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -13,21 +14,20 @@ public class GetIndicatorsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/evidence/indicators/{page:int}/{pageSize:int}", async (EvidenceService service, builder.MapGet("api/evidence/indicators/{page:int}/{pageSize:int}", async (IEvidenceService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetIndicatorsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetIndicatorsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var indicators } => Results.Ok(indicators),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems()),
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a list of indicators")
}) .WithName(typeof(GetIndicatorsEndpoint).ToEndpointName())
.WithDescription("Get a list of indicators") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetIndicatorsEndpoint).ToEndpointName()) .Produces<Indicator[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Indicator[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Evidence)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Evidence);
} }
} }
@@ -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.Evidence;
[ApiVersionTarget(1)]
public class GetMilestoneEvidenceEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/milestone/{milestoneId}", async (string milestoneId, IEvidenceService service,
CancellationToken cancellationToken = default) =>
await service.GetMilestoneEvidencesAsync(milestoneId, cancellationToken) switch
{
{ IsSuccess: true, Value: var evidences } => Results.Ok(evidences),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a list of all uploaded evidence metadata for a specific mliestone")
.WithName(typeof(GetMilestoneEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Evidence[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence)
.CacheOutput();
}
}
@@ -1,34 +0,0 @@
using PostFundManagement.Application.Evidence;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Evidence;
[ApiVersionTarget(1)]
public class GetMilestonesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/milestones/{page:int}/{pageSize:int}", async (EvidenceService service,
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
{
var result = await service.GetMilestonesAsync(Pagination.Create(page, pageSize), cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.BadRequest(result.Errors.ToProblems());
})
.RequireAuthorization()
.WithDescription("Get a list of all milestones")
.WithName(typeof(GetMilestonesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Milestone[]>(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Evidence);
}
}
@@ -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.Evidence;
[ApiVersionTarget(1)]
public class GetProjectEvidenceEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/{projectId}", async (string projectId, IEvidenceService service,
CancellationToken cancellationToken = default) =>
await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var evidence } => Results.Ok(evidence),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems()),
})
.WithDescription("Get a list of all uploaded evidence metadata for a specific project")
.WithName(typeof(GetProjectEvidenceEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Evidence[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence)
.CacheOutput();
}
}
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Evidence;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Application.Extensions;
namespace PostFundManagement.Api.Endpoints.Evidence;
[ApiVersionTarget(1)]
public class GetProjectMilestonesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/evidence/milestones/project/{projectId}", async (string projectId,
EvidenceService service, CancellationToken cancellationToken = default) =>
{
var result = await service.GetMilestonesByProjectIdAsync(projectId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get a list of milestones for a project")
.WithName(typeof(GetProjectMilestonesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Milestone[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Evidence);
}
}
@@ -1,8 +1,10 @@
using PostFundManagement.Application.Evidence; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Evidence; namespace PostFundManagement.Api.Endpoints.Evidence;
@@ -12,20 +14,22 @@ public class RecordIndicatorAmountEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/evidence/indicators/{indicatorId}/actuals/{actualAmount:decimal}", async (string indicatorId, decimal actualAmount, builder.MapPost("api/evidence/indicators/{indicatorId}/actuals/{actualAmount:decimal}", async (string indicatorId, decimal actualAmount,
HttpContext context, EvidenceService service, CancellationToken cancellationToken = default) => HttpContext context, IEvidenceService service, CancellationToken cancellationToken = default) =>
{ await service.RecordIndicatorActualAmountAsync(indicatorId, actualAmount, context.GetUserId(), cancellationToken) switch
var result = await service.RecordIndicatorActualAmountAsync(indicatorId, actualAmount, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok() var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Record actual values for indicators (M08)") .WithDescription("Record actual values for indicators (M08)")
.WithName(typeof(RecordIndicatorAmountEndpoint).ToEndpointName()) .WithName(typeof(RecordIndicatorAmountEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status403Forbidden)
.WithTags(EndpointTags.Evidence); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Evidence);
} }
} }
@@ -0,0 +1,37 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Evidence;
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.Evidence;
[ApiVersionTarget(1)]
public sealed class UpdateEvidenceEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/evidence", async (UpdateEvidenceRequest request, HttpContext context, IEvidenceService service,
CancellationToken cancellationToken = default) =>
await service.UpdateEvidenceAsync(request, 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("Updates a specific evidence record metadata")
.WithName(typeof(UpdateEvidenceEndpoint).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.Evidence);
}
}
@@ -1,8 +1,10 @@
using PostFundManagement.Application.Execution; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -11,21 +13,23 @@ public class ApproveDisbursementEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/execution/disbursements/{disbursementId}/approve", async (string disbursementId, builder.MapPost("api/execution/disbursements/{disbursementId}/approve", async (string disbursementId,
ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) => IExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
{ await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken) switch
var result = await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok() var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
.WithDescription("Approve disbursement and release payment (M06)") .WithDescription("Approve disbursement and release payment (M06)")
.WithName(typeof(ApproveDisbursementEndpoint).ToEndpointName()) .WithName(typeof(ApproveDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status403Forbidden)
.WithTags(EndpointTags.Execution); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Execution; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -12,20 +15,22 @@ public class CreateDisbursementEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request, builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request,
ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) => IExecutionService service, HttpContext context, CancellationToken cancellationToken = default) =>
{ await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems()),
}) })
.WithDescription("Create a new disbursement schedule (M06)") .WithDescription("Create a new disbursement schedule (M06)")
.WithName(typeof(CreateDisbursementEndpoint).ToEndpointName()) .WithName(typeof(CreateDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Execution); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Execution; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Execution;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -11,21 +14,23 @@ public class CreateRiskEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/execution/risks", async (CreateRiskRequest request, ExecutionService service, builder.MapPost("api/execution/risks", async (CreateRiskRequest request, IExecutionService service,
HttpContext context, CancellationToken cancellationToken = default) => HttpContext context, CancellationToken cancellationToken = default) =>
{ await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Add risk to risk register (M09)") .WithDescription("Add risk to risk register (M09)")
.WithName(typeof(CreateRiskEndpoint).ToEndpointName()) .WithName(typeof(CreateRiskEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Execution); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
} }
} }
@@ -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.Execution;
[ApiVersionTarget(1)]
public sealed class GetDisbursementEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/execution/disbursements/{disbursementId}", async (string disbursementId, IExecutionService service,
CancellationToken cancellationToken = default) =>
await service.GetDisbursementAsync(disbursementIdHash: disbursementId, cancellationToken) switch
{
{ IsSuccess: true, Value: var disbursement } => Results.Ok(disbursement),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a specific disbursement")
.WithName(typeof(GetDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Disbursement>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Execution; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Application.Extensions; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -13,21 +14,21 @@ public class GetDisbursementsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/execution/disbursements", async (ExecutionService service, builder.MapGet("api/execution/disbursements", async (IExecutionService service,
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
{ await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var disbursements } => Results.Ok(disbursements),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.NotFound(); .WithDescription("Get a list of disbursements")
}) .WithName(typeof(GetDisbursementsEndpoint).ToEndpointName())
.WithDescription("Get a list of disbursements") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetDisbursementsEndpoint).ToEndpointName()) .Produces<Disbursement[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Disbursement[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Execution)
.WithTags(EndpointTags.Execution); .CacheOutput();
} }
} }
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Execution;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Application.Extensions;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public class GetProjectRisksEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/execution/risks/project/{projectId}", async (string projectId,
ExecutionService service, CancellationToken cancellationToken = default) =>
{
var result = await service.GetProjectRisksAsync(projectId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get a list of risks for a project")
.WithName(typeof(GetProjectRisksEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Risk[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Execution);
}
}
@@ -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.Execution;
[ApiVersionTarget(1)]
public sealed class GetRiskEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/execution/risks/{riskId}", async (string riskId, IExecutionService service,
CancellationToken cancellationToken = default) =>
await service.GetRiskAsync(riskIdHash: riskId, cancellationToken) switch
{
{ IsSuccess: true, Value: var risk } => Results.Ok(risk),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a specific risk")
.WithName(typeof(GetRiskEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Risk>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution)
.CacheOutput();
}
}
@@ -1,9 +1,10 @@
using PostFundManagement.Application.Execution; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution; namespace PostFundManagement.Api.Endpoints.Execution;
@@ -12,20 +13,20 @@ public class GetRisksEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/execution/risks", async (ExecutionService service, builder.MapGet("api/execution/risks", async (IExecutionService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var risks } => Results.Ok(risks),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.NotFound(); .WithDescription("Get a list of all risks")
}) .WithName(typeof(GetRisksEndpoint).ToEndpointName())
.WithDescription("Get a list of all risks") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetRisksEndpoint).ToEndpointName()) .Produces(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Execution)
.WithTags(EndpointTags.Execution); .CacheOutput();
} }
} }
@@ -0,0 +1,34 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class UpdateDisbursementEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/execution/disbursements", async (UpdateDisbursementRequest request, HttpContext context,
IExecutionService service, CancellationToken cancellationToken = default) =>
await service.UpdateDisbursementAsync(request, userId: context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update the details of a specific disbursement")
.WithName(typeof(UpdateDisbursementEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Execution);
}
}
@@ -0,0 +1,34 @@
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;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Execution;
[ApiVersionTarget(1)]
public sealed class UpdateRiskEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/execution/risks", async (UpdateRiskRequest request,
IExecutionService service, CancellationToken cancellationToken = default) =>
await service.UpdateRiskAsync(request, cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update the details of a specific risk")
.WithName(typeof(UpdateRiskEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Execution);
}
}
@@ -1,31 +1,34 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance; using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class CreateOrganisationEndpoint : IEndpoint public sealed class CreateOrganisationEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, HttpContext context, builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, HttpContext context,
GovernanceService service, CancellationToken cancellationToken = default) => IGovernanceService service, CancellationToken cancellationToken = default) =>
{ await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
? Results.Ok() var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Create a new organization profile")
.WithDescription("Create a new organization profile") .WithName(typeof(CreateOrganisationEndpoint).ToEndpointName())
.WithName(typeof(CreateOrganisationEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Governance); .WithTags(EndpointTags.Governance);
} }
} }
@@ -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,8 +1,10 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance; using PostFundManagement.Application.Services.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -11,21 +13,20 @@ public class DashboardEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/dashboard", async (GovernanceService service, builder.MapGet("api/governance/dashboard", async (IGovernanceService service,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
{ await service.GetDashboardAsync(cancellationToken) switch
var result = await service.GetDashboardAsync(cancellationToken); {
{ IsSuccess: true, Value: var dashboard } => Results.Ok(dashboard),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get executive portfolio dashboard metrics and KPIs")
}) .WithName(typeof(DashboardEndpoint).ToEndpointName())
.WithDescription("Get executive portfolio dashboard metrics and KPIs") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(DashboardEndpoint).ToEndpointName()) .Produces<Dashboard>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Dashboard>(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Governance)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Governance);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -11,21 +14,22 @@ public class GetInstrumentsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/instruments", async (Application.Governance.GovernanceService service, builder.MapGet("api/governance/instruments/{programmeId}", async (string programmeId, IGovernanceService service,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
{ await service.GetInstrumentsAsync(programmeId, cancellationToken) switch
var result = await service.GetInstrumentsAsync(cancellationToken); {
{ IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok(result.Value) var r => Results.InternalServerError(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Get all funding instruments")
.WithDescription("Get all funding instruments") .WithName(typeof(GetInstrumentsEndpoint).ToEndpointName())
.WithName(typeof(GetInstrumentsEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces<Instrument[]>(StatusCodes.Status200OK)
.Produces<Instrument[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status403Forbidden)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance); .WithTags(EndpointTags.Governance)
.CacheOutput();
} }
} }
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Governance;
[ApiVersionTarget(1)]
public class GetOrganisationByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/organisations/{organisationId}", async (string organisationId,
GovernanceService service, CancellationToken cancellationToken = default) =>
{
var result = await service.GetOrganisationByIdAsync(organisationId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get organization profile by ID")
.WithName(typeof(GetOrganisationByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Organisation>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Governance);
}
}
@@ -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 class GetOrganisationEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/governance/organisations/{organisationId}", async (string organisationId,
IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.GetOrganisationAsync(organisationId, cancellationToken) switch
{
{ 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())
})
.WithDescription("Get organization profile by ID")
.WithName(typeof(GetOrganisationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Organisation>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -13,21 +14,20 @@ public class GetOrganisationsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/organisations", async (GovernanceService service, builder.MapGet("api/governance/organisations", async (IGovernanceService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var orgnanisations } => Results.Ok(orgnanisations),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a paginated directory of organizations")
}) .WithName(typeof(GetOrganisationsEndpoint).ToEndpointName())
.WithDescription("Get a paginated directory of organizations") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetOrganisationsEndpoint).ToEndpointName()) .Produces<Organisation[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Organisation[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Governance)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Governance);
} }
} }
@@ -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,9 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -12,21 +14,22 @@ public class GetPortfoliosEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/portfolios", async (GovernanceService service, builder.MapGet("api/governance/organisations/{organisationId}/portfolio", async (string organisationId,
CancellationToken cancellationToken = default) => IGovernanceService service, CancellationToken cancellationToken = default) =>
{ await service.GetPorfoliosAsync(organisationId, cancellationToken) switch
var result = await service.GetPorfoliosAsync(cancellationToken); {
{ IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok(result.Value) var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Get all portfolios")
.WithDescription("Get all portfolios") .WithName(typeof(GetPortfoliosEndpoint).ToEndpointName())
.WithName(typeof(GetPortfoliosEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces<Portfolio[]>(StatusCodes.Status200OK)
.Produces<Portfolio[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status403Forbidden)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance); .WithTags(EndpointTags.Governance)
.CacheOutput();
} }
} }
@@ -1,9 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Governance;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Governance; namespace PostFundManagement.Api.Endpoints.Governance;
@@ -12,21 +14,22 @@ public class GetProgrammesEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/governance/programmes", async (GovernanceService service, builder.MapGet("api/governance/programmes/{portfolioId}", async (string portfolioId, IGovernanceService service,
CancellationToken cancellationToken = default) => CancellationToken cancellationToken = default) =>
{ await service.GetProgrammesAsync(portfolioId, cancellationToken) switch
var result = await service.GetProgrammesAsync(cancellationToken); {
{ IsSuccess: true, Value: var programmes } => Results.Ok(programmes),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok(result.Value) var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Get all programmes")
.WithDescription("Get all programmes") .WithName(typeof(GetProgrammesEndpoint).ToEndpointName())
.WithName(typeof(GetProgrammesEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces<Programme[]>(StatusCodes.Status200OK)
.Produces<Programme[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status403Forbidden)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Governance); .WithTags(EndpointTags.Governance)
.CacheOutput();
} }
} }
@@ -0,0 +1,35 @@
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 UpdateOrganisationEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/governance/organisations", async (UpdateOrganisationRequest request,
IGovernanceService service, CancellationToken cancellationToken = default) =>
await service.UpdateOrganisationAsync(request, cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update an existing organization profile")
.WithName(typeof(UpdateOrganisationEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Governance);
}
}
@@ -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,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -12,20 +15,23 @@ public class CreateAwardEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/grants/awards", async (CreateAwardRequest request, HttpContext context, builder.MapPost("api/grants/awards", async (CreateAwardRequest request, HttpContext context,
GrantsService service, CancellationToken cancellationToken = default) => IGrantsService service, CancellationToken cancellationToken = default) =>
{ await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
}) var r => Results.InternalServerError(r.Errors.ToProblems())
.WithDescription("Register an approved funding award (M01)") })
.WithName(typeof(CreateAwardEndpoint).ToEndpointName()) .WithDescription("Register an approved funding award (M01)")
.MapToApiVersion(new ApiVersion(1)) .WithName(typeof(CreateAwardEndpoint).ToEndpointName())
.Produces(StatusCodes.Status200OK) .MapToApiVersion(new ApiVersion(1))
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants); .Produces(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -12,20 +15,24 @@ public class CreateBudgetEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, HttpContext context, builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, HttpContext context,
GrantsService service, CancellationToken cancellationToken = default) => IGrantsService service, CancellationToken cancellationToken = default) =>
{ await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
? Results.Ok() var r when r.HasError<NotFoundError>() => Results.NotFound(),
: Results.BadRequest(result.Errors.ToProblems()); var r when r.HasError<CreateError>() => Results.BadRequest(),
}) var r => Results.InternalServerError(r.Errors.ToProblems())
.WithDescription("Create a new budget line (M05)") })
.WithName(typeof(CreateBudgetEndpoint).ToEndpointName()) .WithDescription("Create a new budget line (M05)")
.MapToApiVersion(new ApiVersion(1)) .WithName(typeof(CreateBudgetEndpoint).ToEndpointName())
.Produces(StatusCodes.Status200OK) .MapToApiVersion(new ApiVersion(1))
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound)
.WithTags(EndpointTags.Grants); .Produces(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -12,20 +15,21 @@ public class CreateContractEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/grants/contracts", async (CreateContractRequest request, HttpContext context, builder.MapPost("api/grants/contracts", async (CreateContractRequest request, HttpContext context,
GrantsService service, CancellationToken cancellationToken = default) => IGrantsService service, CancellationToken cancellationToken = default) =>
{ await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateContractAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Create a contract record from an approved award (M02)") .WithDescription("Create a contract record from an approved award (M02)")
.WithName(typeof(CreateContractEndpoint).ToEndpointName()) .WithName(typeof(CreateContractEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants); .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Services.Grants;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -12,20 +15,20 @@ public class ExecuteContractEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/grants/contracts/execute", async (ExecuteContractRequest request, HttpContext context, builder.MapPost("api/grants/contracts/execute", async (ExecuteContractRequest request, HttpContext context,
GrantsService service, CancellationToken cancellationToken = default) => IGrantsService service, CancellationToken cancellationToken = default) =>
{ await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
? Results.Ok() var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Execute the contract and lock the contractual version")
.WithDescription("Execute the contract and lock the contractual version") .WithName(typeof(ExecuteContractEndpoint).ToEndpointName())
.WithName(typeof(ExecuteContractEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants); .WithTags(EndpointTags.Grants);
} }
} }
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Grants;
[ApiVersionTarget(1)]
public class GetAwardByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/awards/{awardId}", async (string awardId, GrantsService service,
CancellationToken cancellationToken = default) =>
{
var result = await service.GetAwardByIdAsync(awardId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get award by ID")
.WithName(typeof(GetAwardByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Award>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants);
}
}
@@ -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.Grants;
[ApiVersionTarget(1)]
public class GetAwardEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/awards/{awardId}", async (string awardId, IGrantsService service,
CancellationToken cancellationToken = default) =>
await service.GetAwardAsync(awardId, cancellationToken) switch
{
{ IsSuccess: true, Value: var award } => Results.Ok(award),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get award by ID")
.WithName(typeof(GetAwardEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Award>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Application.Extensions; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -13,21 +14,20 @@ public class GetAwardsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/grants/awards", async (GrantsService service, builder.MapGet("api/grants/awards", async (IGrantsService service,
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
{ await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var awards } => Results.Ok(awards),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a list of awards")
}) .WithName(typeof(GetAwardsEndpoint).ToEndpointName())
.WithDescription("Get a list of awards") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetAwardsEndpoint).ToEndpointName()) .Produces<Award[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Award[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Grants)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Grants;
[ApiVersionTarget(1)]
public class GetBudgetByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/budgets/{budgetId}", async (string budgetId, GrantsService service,
CancellationToken cancellationToken = default) =>
{
var result = await service.GetBudgetByIdAsync(budgetId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get budget by ID")
.WithName(typeof(GetBudgetByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Budget>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants);
}
}
@@ -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.Grants;
[ApiVersionTarget(1)]
public class GetBudgetEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/budgets/{budgetId}", async (string budgetId, IGrantsService service,
CancellationToken cancellationToken = default) =>
await service.GetBudgetAsync(budgetId, cancellationToken) switch
{
{ IsSuccess: true, Value: var budget } => Results.Ok(budget),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get budget by ID")
.WithName(typeof(GetBudgetEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Budget>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Grants; using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Application.Extensions; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -13,21 +14,20 @@ public class GetBudgetsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/grants/budgets", async (GrantsService service, builder.MapGet("api/grants/budgets", async (IGrantsService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var budgets } => Results.Ok(budgets),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get all budgets")
}) .WithName(typeof(GetBudgetsEndpoint).ToEndpointName())
.WithDescription("Get all budgets") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetBudgetsEndpoint).ToEndpointName()) .Produces<Budget[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Budget[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Grants)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,32 +0,0 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Grants;
[ApiVersionTarget(1)]
public class GetContractByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/contracts/{contractId}", async (string contractId, GrantsService service,
CancellationToken cancellationToken = default) =>
{
var result = await service.GetContractByIdAsync(contractId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get contract by ID")
.WithName(typeof(GetContractByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Contract>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Grants);
}
}
@@ -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.Grants;
[ApiVersionTarget(1)]
public class GetContractEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/grants/contracts/{contractId}", async (string contractId, IGrantsService service,
CancellationToken cancellationToken = default) =>
await service.GetContractAsync(contractId, cancellationToken) switch
{
{ IsSuccess: true, Value: var contract } => Results.Ok(contract),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get contract by ID")
.WithName(typeof(GetContractEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Contract>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Grants)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Grants;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Grants; namespace PostFundManagement.Api.Endpoints.Grants;
@@ -13,21 +14,20 @@ public class GetContractsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/grants/contracts", async (GrantsService service, builder.MapGet("api/grants/contracts", async (IGrantsService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var contracts } => Results.Ok(contracts),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get all contracts")
}) .WithName(typeof(GetContractsEndpoint).ToEndpointName())
.WithDescription("Get all contracts") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetContractsEndpoint).ToEndpointName()) .Produces<Contract[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Contract[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Grants)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Grants);
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -12,20 +15,21 @@ public class CreateActivityEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/projects/activities", async (CreateActivityRequest request, builder.MapPost("api/projects/activities", async (CreateActivityRequest request,
HttpContext context, ProjectService service, CancellationToken cancellationToken = default) => HttpContext context, IProjectService service, CancellationToken cancellationToken = default) =>
{ await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } result => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r => Results.InternalServerError(r.Errors.ToProblems())
}) })
.WithDescription("Add a new activity under project (M04)") .WithDescription("Add a new activity under project (M04)")
.WithName(typeof(CreateActivityEndpoint).ToEndpointName()) .WithName(typeof(CreateActivityEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects); .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
} }
} }
@@ -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,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -12,20 +15,23 @@ public class CreateProjectEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/projects", async (CreateProjectRequest request, HttpContext context, builder.MapPost("api/projects", async (CreateProjectRequest request, HttpContext context,
ProjectService service, CancellationToken cancellationToken = default) => IProjectService service, CancellationToken cancellationToken = default) =>
{ await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch
var result = await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } result => Results.Ok(),
return result.IsSuccess var r when r.HasError<NotFoundError>() => Results.NotFound(),
? Results.Ok() var r when r.HasError<ValidationError>() => Results.BadRequest(r.Errors.ToProblems()),
: Results.BadRequest(result.Errors.ToProblems()); var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
}) var r => Results.InternalServerError(r.Errors.ToProblems())
.WithDescription("Create a new project (M04)") })
.WithName(typeof(CreateProjectEndpoint).ToEndpointName()) .WithDescription("Create a new project (M04)")
.MapToApiVersion(new ApiVersion(1)) .WithName(typeof(CreateProjectEndpoint).ToEndpointName())
.Produces(StatusCodes.Status200OK) .MapToApiVersion(new ApiVersion(1))
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status404NotFound)
.WithTags(EndpointTags.Projects); .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
} }
} }
@@ -1,8 +1,10 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -12,19 +14,21 @@ public class GetActivitiesEndpoint : IEndpoint
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/projects/{projectId}/activities", async (string projectId, builder.MapGet("api/projects/{projectId}/activities", async (string projectId,
ProjectService service, CancellationToken cancellationToken = default) => IProjectService service, CancellationToken cancellationToken = default) =>
{ await service.GetActivitiesAsync(projectId, cancellationToken) switch
var result = await service.GetActivitiesAsync(projectId, cancellationToken); {
{ IsSuccess: true, Value: var activities } => Results.Ok(activities),
return result.IsSuccess var r when r.HasError<IdDecodeError>() => Results.Forbid(),
? Results.Ok(result.Value) var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.NotFound(); })
}) .WithDescription("Get a list of activities for a project")
.WithDescription("Get a list of activities for a project") .WithName(typeof(GetActivitiesEndpoint).ToEndpointName())
.WithName(typeof(GetActivitiesEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces<Domain.Models.Activity[]>(StatusCodes.Status200OK)
.Produces<Domain.Models.Activity[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects); .Produces<Problems>(StatusCodes.Status500InternalServerError)
.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.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class GetActivityEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/activities/{activityId}", async (string activityId,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.GetActivityAsync(activityId, cancellationToken) switch
{
{ IsSuccess: true, Value: var activity } => Results.Ok(activity),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets a specific activity by it's id")
.WithName(typeof(GetActivityEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Models.Activity>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.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 class GetMilestoneEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/milestones/{milestoneId}", async (string milestoneId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetMilestoneAsync(milestoneId, cancellationToken) switch
{
{ IsSuccess: true, Value: var milestone } => Results.Ok(milestone),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets a specific milestone")
.WithName(typeof(GetMilestoneEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Milestone>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -0,0 +1,33 @@
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 class GetMilestonesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
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())
})
.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)
.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,32 +0,0 @@
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class GetProjectByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}", async (string projectId, ProjectService service,
CancellationToken cancellationToken = default) =>
{
var result = await service.GetProjectByIdAsync(projectId, cancellationToken);
return result.IsSuccess
? Results.Ok(result.Value)
: Results.NotFound();
})
.WithDescription("Get project by ID")
.WithName(typeof(GetProjectByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Project[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -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 class GetProjectEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetProjectAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var project } => Results.Ok(project),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get project by ID")
.WithName(typeof(GetProjectEndpoint).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,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 class GetProjectIndicatorsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/indicators/project/{projectId}", async (string projectId, IProjectService service,
CancellationToken cancellationToken = default) =>
await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var indicators } => Results.Ok(indicators),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get get a detailed list of project indicators")
.WithName(typeof(GetProjectIndicatorsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Indicator[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.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 class GetProjectMilestonesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/milestones", async (string projectId,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.GetProjectMilestonesAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var milestones } => Results.Ok(milestones),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a list of milestones for a project")
.WithName(typeof(GetProjectMilestonesEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Milestone[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.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 class GetProjectRisksEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId}/risks", async (string projectId,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.GetProjectRisksAsync(projectId, cancellationToken) switch
{
{ IsSuccess: true, Value: var risks } => Results.Ok(risks),
var r when r.HasError<IdDecodeError>() => Results.Forbid(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Get a list of risks for a project")
.WithName(typeof(GetProjectRisksEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Risk[]>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects)
.CacheOutput();
}
}
@@ -1,10 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
@@ -13,21 +14,20 @@ public class GetProjectsEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapGet("api/projects", async (ProjectService service, builder.MapGet("api/projects", async (IProjectService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken) switch
var result = await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken); {
{ IsSuccess: true, Value: var projects } => Results.Ok(projects),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.BadRequest(result.Errors.ToProblems()); .WithDescription("Get a list of projects")
}) .WithName(typeof(GetProjectsEndpoint).ToEndpointName())
.WithDescription("Get a list of projects") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetProjectsEndpoint).ToEndpointName()) .Produces<Project[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces<Project[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .WithTags(EndpointTags.Projects)
.Produces(StatusCodes.Status401Unauthorized) .CacheOutput();
.WithTags(EndpointTags.Projects);
} }
} }
@@ -0,0 +1,31 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public sealed class UpdateMilestoneEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/milestone", async (UpdateMilestoneRequest request, IProjectService service,
HttpContext context, CancellationToken cancellationToken = default) =>
await service.UpdateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Update project milestone details")
.WithName(typeof(UpdateMilestoneEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -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,31 +1,34 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Projects; using PostFundManagement.Application.Services.Projects;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Projects; namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)] [ApiVersionTarget(1)]
public class UpdateProjectEndpoint : IEndpoint public sealed class UpdateProjectEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/projects/status", async (UpdateProjectStatusRequest request, HttpContext context, builder.MapPut("api/projects", async (UpdateProjectRequest request,
ProjectService service, CancellationToken cancellationToken = default) => IProjectService service, HttpContext context, CancellationToken cancellationToken = default) =>
{ await service.UpdateProjectAsync(request, userId: context.GetUserId(), cancellationToken) switch
var result = await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken); {
{ IsSuccess: true } => Results.Ok(),
return result.IsSuccess var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
? Results.Ok() var r => Results.InternalServerError(r.Errors.ToProblems())
: Results.BadRequest(result.Errors.ToProblems()); })
}) .WithDescription("Update project details")
.WithDescription("Update project status (M04)") .WithName(typeof(UpdateProjectEndpoint).ToEndpointName())
.WithName(typeof(UpdateProjectEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1))
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status200OK) .Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects); .WithTags(EndpointTags.Projects);
} }
} }
@@ -0,0 +1,35 @@
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 class UpdateProjectStatusEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPut("api/projects/status", async (UpdateProjectStatusRequest request, HttpContext context,
IProjectService service, CancellationToken cancellationToken = default) =>
await service.UpdateProjectStatusAsync(request, 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 project status (M04)")
.WithName(typeof(UpdateProjectStatusEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Projects);
}
}
@@ -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();
}
}
@@ -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.Users;
[ApiVersionTarget(1)]
public sealed class GetUserEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/users/{userId:Guid}", async (Guid userId, IUserService service,
HttpContext context, CancellationToken cancellationToken = default) =>
await service.GetUserAsync(userId, cancellationToken) switch
{
{ IsSuccess: true, Value: var user } => Results.Ok(user),
var r when r.HasError<NotFoundError>() => Results.NotFound(),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Gets the metadata of a single user.")
.WithName(typeof(GetUserEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<User>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Users)
.CacheOutput();
}
}
@@ -1,10 +1,12 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Users; using PostFundManagement.Application.Services.Users;
using PostFundManagement.Domain; using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Models;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Users; namespace PostFundManagement.Api.Endpoints.Users;
@@ -13,21 +15,21 @@ public class GetUsersEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/users/{page:int}/{pageSize:int}", async (UserFilterRequest request, UserService service, builder.MapPost("api/users/{page:int}/{pageSize:int}", async (UserFilterRequest request, IUserService service,
int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) =>
{ await service.GetUsersAsync(Pagination.Create(page, pageSize), request, cancellationToken) switch
var result = await service.GetUsersAsync(Pagination.Create(page, pageSize), request, cancellationToken); {
{ IsSuccess: true, Value: var users } => Results.Ok(users),
return result.IsSuccess var r => Results.InternalServerError(r.Errors.ToProblems())
? Results.Ok(result.Value) })
: Results.NotFound(); .WithDescription("Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting")
}) .WithName(typeof(GetUsersEndpoint).ToEndpointName())
.WithDescription("Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting") .MapToApiVersion(new ApiVersion(1))
.WithName(typeof(GetUsersEndpoint).ToEndpointName()) .Produces<User[]>(StatusCodes.Status200OK)
.MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status404NotFound)
.Produces<User[]>(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status404NotFound) .Produces<Problems>(StatusCodes.Status500InternalServerError)
.Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Users)
.WithTags(EndpointTags.Users); .CacheOutput();
} }
} }
@@ -1,8 +1,11 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Users; using PostFundManagement.Application.Services.Communications.Events;
using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api; using PostFundManagement.Domain.Errors;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Security;
using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails;
namespace PostFundManagement.Api.Endpoints.Users; namespace PostFundManagement.Api.Endpoints.Users;
@@ -11,21 +14,28 @@ public class SyncUserEndpoint : IEndpoint
{ {
public void Map(IEndpointRouteBuilder builder) public void Map(IEndpointRouteBuilder builder)
{ {
builder.MapPost("api/users/sync", async (HttpContext context, builder.MapPost("api/integration/sync", async (IJobOrchestrator jobOrchestrator,
UserService service, CancellationToken cancellationToken = default) => HttpContext context, IUserService service, CancellationToken cancellationToken = default) =>
{ {
var result = await service.SyncUserAsync(context.GetUserId(), context.GetUserEmail(), cancellationToken); var result = await service.SyncUserAsync(context.GetUserId(), context.GetUserEmail(), cancellationToken);
return result.IsSuccess if (result.IsSuccess)
? Results.Ok() await jobOrchestrator.SendAsync(SyncUserEvent.Create(), cancellationToken);
: Results.BadRequest(result.Errors.ToProblems());
return result switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<CreateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
};
}) })
.WithDescription("Synchronise caller user profile using id_token's SID, inserting/updating user details in database") .WithDescription("Trigger integration and data synchronization in the background")
.WithName(typeof(SyncUserEndpoint).ToEndpointName()) .WithName(typeof(SyncUserEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1)) .MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status200OK)
.Produces<Microsoft.AspNetCore.Mvc.ProblemDetails>(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized) .Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Users); .Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Users)
.CacheOutput();
} }
} }
@@ -0,0 +1,35 @@
using PostFundManagement.Application.Abstractions.Services;
using PostFundManagement.Application.Extensions;
using PostFundManagement.Domain;
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.Users;
[ApiVersionTarget(1)]
public sealed class UpdateUserStatusEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/users/{userId:Guid}/status/{status:int}", async (Guid userId, ActivityStatus status,
IUserService service, CancellationToken cancellationToken = default) =>
await service.UpdateUserStatusAsync(userId, status, cancellationToken) switch
{
{ IsSuccess: true } => Results.Ok(),
var r when r.HasError<UpdateError>() => Results.BadRequest(r.Errors.ToProblems()),
var r => Results.InternalServerError(r.Errors.ToProblems())
})
.WithDescription("Changes the Activity status of the user in the system")
.WithName(typeof(UpdateUserStatusEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.Produces<Problems>(StatusCodes.Status400BadRequest)
.Produces<Problems>(StatusCodes.Status500InternalServerError)
.WithTags(EndpointTags.Users);
}
}
@@ -10,6 +10,14 @@
<PackageLicenseFile>..\LICENSE</PackageLicenseFile> <PackageLicenseFile>..\LICENSE</PackageLicenseFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1701;1702;AV0029;AV0030;</NoWarn>
</PropertyGroup>
<!-- Security (IODC)--> <!-- Security (IODC)-->
<ItemGroup> <ItemGroup>
<PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" /> <PackageReference Include="IdentityModel.AspNetCore" Version="4.3.0" />
@@ -43,7 +51,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AccessTokenClient.Extensions" Version="5.1.0" /> <PackageReference Include="AccessTokenClient.Extensions" Version="5.1.0" />
<PackageReference Include="Asp.Versioning.Abstractions" Version="10.2.1" /> <PackageReference Include="Asp.Versioning.Abstractions" Version="10.2.1" />
<PackageReference Include="Asp.Versioning.Http" Version="10.2.2" /> <PackageReference Include="Asp.Versioning.Http" Version="10.2.3" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" /> <PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" Version="10.2.1" />
<Using Include="Asp.Versioning" /> <Using Include="Asp.Versioning" />
@@ -53,7 +61,7 @@
<!-- API Documentation --> <!-- API Documentation -->
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.11" />
<PackageReference Include="Scalar.AspNetCore" Version="2.17.1" /> <PackageReference Include="Scalar.AspNetCore" Version="2.17.2" />
<Using Include="Scalar.AspNetCore" /> <Using Include="Scalar.AspNetCore" />
<Using Include="Microsoft.OpenApi" /> <Using Include="Microsoft.OpenApi" />
+78 -8
View File
@@ -1,6 +1,5 @@
using PostFundManagement.Api.Extensions;
using PostFundManagement.Application.Api.Filters;
using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Extensions;
using PostFundManagement.Application.Filters;
using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Health; using PostFundManagement.Domain.Health;
using PostFundManagement.Domain.Mediator; using PostFundManagement.Domain.Mediator;
@@ -16,6 +15,9 @@ builder.Services.AddScoped<ActiveUserFilter>();
builder.Services.AddEndpoints(Assembly.GetExecutingAssembly()); builder.Services.AddEndpoints(Assembly.GetExecutingAssembly());
builder.Services.AddApiServices(builder.Configuration); builder.Services.AddApiServices(builder.Configuration);
builder.Services.AddSharedServices();
builder.Services.AddRegisterServicesWithCaching(builder.Configuration);
builder.Services.AddEndpointAndServiceCaching(builder.Configuration);
builder.Services.AddMediator(); builder.Services.AddMediator();
builder.Services.AddSecurityApiSdk(builder.Configuration); builder.Services.AddSecurityApiSdk(builder.Configuration);
@@ -28,7 +30,6 @@ builder.Services.AddQuartzScheduler(DefaultSchedulerName, builder.Configuration)
builder.Services.AddEmailServices(builder.Configuration); builder.Services.AddEmailServices(builder.Configuration);
builder.Services.AddHashServices(builder.Configuration); builder.Services.AddHashServices(builder.Configuration);
builder.Services.AddGarageS3(builder.Configuration); builder.Services.AddGarageS3(builder.Configuration);
builder.Services.AddRegisterServices();
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddDataProtectionDatabase(builder.Configuration); builder.Services.AddDataProtectionDatabase(builder.Configuration);
@@ -39,6 +40,63 @@ builder.Services.AddHealthChecks()
.AddCheck<PostgresHealthCheck>("PostgresDatabase") .AddCheck<PostgresHealthCheck>("PostgresDatabase")
.AddCheck("Self", () => HealthCheckResult.Healthy()); .AddCheck("Self", () => HealthCheckResult.Healthy());
IEnumerable<string> identityScopes = ["openid", "profile", "roles", "email", "address"];
builder.Services.AddValidation()
.AddEndpointValidation();
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, context, cancellationToken) =>
{
document.Components ??= new OpenApiComponents();
document.Components?.SecuritySchemes!["OAuth2"] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Description = "Interactive OAuth2 flow for authentication and authorization",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri($"{builder.Configuration["SecuritySettings:Authority"]}/connect/authorize"),
TokenUrl = new Uri($"{builder.Configuration["SecuritySettings:Authority"]}/connect/token"),
Scopes = new Dictionary<string, string>
{
{ "openid", "OpenID Connect" },
{ "profile", "User Profile" },
{ "roles", "User Roles" },
{ "email", "User Email" },
{ "address", "User Address" },
{ builder.Configuration["SecuritySettings:Audience"]!, "PFM API Scope" }
}
}
}
};
return Task.CompletedTask;
});
options.AddOperationTransformer((operation, context, cancellationToken) =>
{
operation.Security ??= [];
var schemeReference = new OpenApiSecuritySchemeReference("OAuth2");
operation.Security.Add(new OpenApiSecurityRequirement
{
[schemeReference] = [.. identityScopes, builder.Configuration["SecuritySettings:Audience"]!]
});
return Task.CompletedTask;
});
});
builder.Services.AddCors(options => options.AddPolicy("AllowFrontend", policy =>
policy.WithOrigins("https://localhost:3000", "https://localhost:8076", "https://console.ifri.khongisa.co.za")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
var app = builder.Build(); var app = builder.Build();
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>(); var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
@@ -51,20 +109,21 @@ app.UseHsts();
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseRouting(); app.UseRouting();
app.UseCors("AllowFrontend");
app.UseOutputCache();
app.UseAuthentication(); app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
ApiVersionSet versionSet = app.NewApiVersionSet("v1") ApiVersionSet versionSet = app.NewApiVersionSet("v1")
.HasApiVersion(new ApiVersion(1)) .HasApiVersion(new ApiVersion(1))
.HasApiVersion(new ApiVersion(2))
.ReportApiVersions() .ReportApiVersions()
.Build(); .Build();
var versionGroups = new Dictionary<int, RouteGroupBuilder> var versionGroups = new Dictionary<int, RouteGroupBuilder>
{ {
{ {
1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet) 1, app.MapGroup("v{version:apiVersion}").WithApiVersionSet(versionSet)
.RequireAuthorization().AddEndpointFilter<ActiveUserFilter>() .RequireAuthorization("ApiScope").AddEndpointFilter<ActiveUserFilter>()
} }
}; };
@@ -87,14 +146,25 @@ var apiVersions = app.DescribeApiVersions()
.ToList(); .ToList();
foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.ApiVersion.MajorVersion)) foreach (var description in app.DescribeApiVersions().OrderByDescending(o => o.ApiVersion.MajorVersion))
{
app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) => app.MapScalarApiReference($"/openapi/{description.GroupName}", (options, context) =>
{ {
options.AddServer(new ScalarServer($"https://{context.Request.Host}")); options.AddServer(new ScalarServer($"https://{context.Request.Host}"));
options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json"); options.WithOpenApiRoutePattern($"/openapi/{description.GroupName}.json");
options.WithTheme(ScalarTheme.DeepSpace); options.WithTheme(ScalarTheme.DeepSpace);
options.Agent = new ScalarAgentOptions { Disabled = true }; options.Agent = new ScalarAgentOptions { Disabled = true };
options.Authentication = new ScalarAuthenticationOptions { PreferredSecuritySchemes = ["Bearer"] }; options.AddPreferredSecuritySchemes("OAuth2")
.AddOAuth2Flows("OAuth2", flow =>
{
flow.AuthorizationCode = new AuthorizationCodeFlow
{
ClientId = builder.Configuration["SecuritySettings:ClientId"]!,
Pkce = Pkce.Sha256,
SelectedScopes = [.. identityScopes, builder.Configuration["SecuritySettings:Audience"]!]
};
});
}); });
}
var latestVersionGroup = apiVersions.FirstOrDefault()?.GroupName ?? "v1"; var latestVersionGroup = apiVersions.FirstOrDefault()?.GroupName ?? "v1";
+11 -1
View File
@@ -19,7 +19,7 @@
}, },
"Monitoring": { "Monitoring": {
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889", "Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
"ServiceName": "MidrandBooks.DEV" "ServiceName": "Pfm.DEV"
}, },
"SecuritySettings": { "SecuritySettings": {
"Authority": "https://sts.security.khongisa.co.za", "Authority": "https://sts.security.khongisa.co.za",
@@ -31,5 +31,15 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"EmailProcessorCron": "0 */5 * * * ?",
"Email": {
"SmtpServer": "smtp.example.com",
"SmtpPort": 587,
"SenderEmail": "noreply@example.com",
"SenderName": "Example"
},
"HasherSettings": {
"MinHashLength": 11
},
"AllowedHosts": "*" "AllowedHosts": "*"
} }
@@ -1,4 +1,5 @@
using PostFundManagement.Application.Shared; using PostFundManagement.Application.Services.Shared;
using PostFundManagement.Domain.Errors;
namespace PostFundManagement.Application.Abstractions; namespace PostFundManagement.Application.Abstractions;
@@ -9,17 +10,16 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
protected abstract string BucketName { get; } protected abstract string BucketName { get; }
protected abstract string CdnBaseUrl { get; } protected abstract string CdnBaseUrl { get; }
public virtual async Task<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default) public virtual async ValueTask<Result<string>> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<ValueTask<Result<string>>>)(async () =>
try
{ {
if (string.IsNullOrWhiteSpace(BucketName)) if (string.IsNullOrWhiteSpace(BucketName))
return Result.Fail<string>("Bucket name is not configured."); return Result.Fail<string>(Domain.Errors.ValidationError.Create("Bucket name is not configured."));
if (string.IsNullOrWhiteSpace(CdnBaseUrl)) if (string.IsNullOrWhiteSpace(CdnBaseUrl))
return Result.Fail<string>("CDN base URL is not configured."); return Result.Fail<string>(Domain.Errors.ValidationError.Create("CDN base URL is not configured."));
using var stream = new MemoryStream(); await using var stream = new MemoryStream();
await fileStream.CopyToAsync(stream, cancellationToken); await fileStream.CopyToAsync(stream, cancellationToken);
await fileStream.DisposeAsync(); await fileStream.DisposeAsync();
@@ -28,8 +28,8 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
var fileHash = HashService.StreamToSha256Hash(stream); var fileHash = HashService.StreamToSha256Hash(stream);
if(string.IsNullOrWhiteSpace(fileHash)) if (string.IsNullOrWhiteSpace(fileHash))
return Result.Fail<string>("Failed to compute file hash."); return Result.Fail<string>(CreateError.Create("Failed to compute file hash."));
var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}"; var fileKey = $"{fileHash.ToLower(CultureInfo.InvariantCulture)}{Path.GetExtension(fileName)}";
@@ -46,38 +46,24 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3)
var response = await Client.PutObjectAsync(putRequest, cancellationToken); var response = await Client.PutObjectAsync(putRequest, cancellationToken);
return response.HttpStatusCode != System.Net.HttpStatusCode.OK return response.HttpStatusCode != HttpStatusCode.OK
? Result.Fail<string>($"Failed to upload {fileName} to S3.") ? Result.Fail<string>(CreateError.Create($"Failed to upload {fileName} to S3."))
: Result.Ok($"{CdnBaseUrl}/{fileKey}"); : Result.Ok($"{CdnBaseUrl}/{fileKey}");
} }));
catch (Exception ex)
{
return Result.Fail<string>(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex));
}
}
public virtual async Task<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) public virtual async ValueTask<Result> DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) =>
{ await Result.Try((Func<ValueTask<Result>>)(async () =>
try
{ {
if (string.IsNullOrWhiteSpace(BucketName)) if (string.IsNullOrWhiteSpace(BucketName))
return Result.Fail("Bucket name is not configured."); return Result.Fail(NotFoundError.Create("Bucket name is not configured."));
var deleteRequest = new DeleteObjectRequest var response = await Client.DeleteObjectAsync(new DeleteObjectRequest
{ {
BucketName = BucketName, BucketName = BucketName,
Key = fileKey Key = fileKey
}; }, cancellationToken);
var response = await Client.DeleteObjectAsync(deleteRequest, cancellationToken); return Result.FailIf(response.HttpStatusCode == HttpStatusCode.NoContent,
UpdateError.Create($"Failed to delete {fileKey} from S3."));
return response.HttpStatusCode != System.Net.HttpStatusCode.NoContent }));
? Result.Fail($"Failed to delete {fileKey} from S3.")
: Result.Ok();
}
catch (Exception ex)
{
return Result.Fail(new Error($"Error deleting {fileKey} from S3: {ex.Message}").CausedBy(ex));
}
}
} }
@@ -0,0 +1,9 @@
namespace PostFundManagement.Application.Abstractions;
public abstract class ServiceCacheBase
{
protected readonly DistributedCacheEntryOptions cacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(2)
};
}
@@ -0,0 +1,16 @@
using PostFundManagement.Application.Services.Communications;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services;
public interface ICommunicationsService
{
ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Invite>> GetApplicantInviteAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
ValueTask<Result<Invite[]>> GetApplicantInvitesAsync(Guid applicantUserId, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,30 @@
using PostFundManagement.Application.Services.Evidence;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services;
public interface IEvidenceService
{
ValueTask<Result> CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default);
ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default);
ValueTask<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default);
ValueTask<Result<Indicator>> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken);
ValueTask<Result<Evidence>> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default);
ValueTask<Result<Evidence[]>> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, 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,6 @@
namespace PostFundManagement.Application.Abstractions.Services;
public interface IIdentityService
{
ValueTask<Result> UserIsAuthorisedAsync(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);
}
@@ -0,0 +1,16 @@
using PostFundManagement.Application.Services.Users;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Models;
namespace PostFundManagement.Application.Abstractions.Services;
public interface IUserService
{
ValueTask<Result> SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default);
ValueTask<Result<User[]>> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default);
ValueTask<Result> UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default);
ValueTask<Result<User>> GetUserAsync(Guid userId, CancellationToken cancellationToken);
}
@@ -1,70 +0,0 @@
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Application.Communications;
public sealed class CommunicationsService(IDbContextFactory<ApplicationDbContext> contextFactory) : IService
{
public async ValueTask<Result<Invite>> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var invite = new Domain.Entities.Invite
{
OrganisationId = request.OrganisationId,
InvitedOrganisationId = request.InvitedOrganisationId,
AwardId = request.AwardId,
ContractId = request.ContractId,
Recipient = request.Recipient,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
};
context.Invites.Add(invite);
context.Notifications.Add(new Domain.Entities.Notification
{
OrganisationId = request.OrganisationId,
Recipient = request.Recipient,
Platform = request.Platform,
Status = NotificationStatus.Pending,
Subject = "Invitation to Onboard",
Message = request.Message,
CreatedAt = DateTime.UtcNow
});
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok(invite.Map())
: Result.Fail("Failed to complete the insite distribution");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Invite[]>> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var invites = await context.Invites.AsNoTracking()
.OrderByDescending(i => i.CreatedAt)
.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(invites?.Select(i => i.Map()).ToArray() ?? []);
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -1,9 +0,0 @@
using PostFundManagement.Application.Abstractions;
using PostFundManagement.Domain.Abstractions;
namespace PostFundManagement.Application.Communications.Events;
public class SyncUserEvent : EventBase, IEvent
{
public string Name { get; set; } = nameof(SyncUserEvent);
}
@@ -1,5 +0,0 @@
using PostFundManagement.Domain;
namespace PostFundManagement.Application.Communications;
public record InviteRequest(long OrganisationId, long InvitedOrganisationId, long AwardId, long ContractId, Guid Recipient, string Message, NotificationPlatform Platform);
@@ -1,233 +0,0 @@
using PostFundManagement.Application.Shared;
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Domain.Models;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Application.Evidence;
public sealed class EvidenceService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService,
[FromKeyedServices(Constants.EvidenceBucketName)] IS3Service s3Service) : IService
{
public async ValueTask<Result> UploadEvidenceFileAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default)
{
try
{
_ = long.TryParse(form["projectId"], out var projectId);
_ = long.TryParse(form["milestoneId"], out var milestoneId);
_ = long.TryParse(form["indicatorId"], out var indicatorId);
_ = long.TryParse(form["activityId"], out var activityId);
_ = Enum.TryParse<EvidenceType>(form["type"], out var type);
using var stream = file.OpenReadStream();
var uploadResult = await s3Service.UploadFileAsync(file.FileName, stream, file.ContentType, cancellationToken);
if (uploadResult.IsFailed)
return Result.Fail(uploadResult.Errors.FirstOrDefault()?.Message ?? "File upload failed.");
var documentUrl = uploadResult.Value;
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var evidence = new Domain.Entities.Evidence
{
ProjectId = projectId,
MilestoneId = milestoneId,
IndicatorId = indicatorId,
ActivityId = activityId,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow,
Version = 1,
Type = type,
DocumentUrl = documentUrl,
Status = ApprovalStatus.Pending
};
context.Evidences.Add(evidence);
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok()
: Result.Fail("Evidence upload failed");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default)
{
try
{
var decodeResult = hashService.DecodeLongIdHash(indicatorIdHash);
if (decodeResult.IsFailed)
return Result.Fail(decodeResult.Errors);
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var totalUpdated = await context.Indicators.Where(i => i.Id == decodeResult.Value)
.ExecuteUpdateAsync(f => f
.SetProperty(f => f.ActualAmount, amount)
.SetProperty(f => f.CreatedBy, userId), cancellationToken);
return totalUpdated > 0
? Result.Ok()
: Result.Fail("Failed to record actual amount");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Milestone[]>> GetMilestonesByProjectIdAsync(string projectIdHash, CancellationToken cancellationToken = default)
{
try
{
var decodeResult = hashService.DecodeLongIdHash(projectIdHash);
if (decodeResult.IsFailed)
return Result.Fail(decodeResult.Errors);
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var milestones = await context.Milestones.AsNoTracking()
.Where(m => m.ProjectId == decodeResult.Value)
.OrderBy(m => m.DueAt)
.ToListAsync(cancellationToken);
return Result.Ok(milestones?.Select(i => i.Map()).ToArray() ?? []);
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Milestone[]>> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var milestones = await context.Milestones.AsNoTracking()
.OrderByDescending(m => m.CreatedAt)
.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(milestones?.Select(i => i.Map()).ToArray() ?? []);
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Indicator[]>> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var indicators = await context.Indicators.AsNoTracking()
.OrderByDescending(i => i.CreatedAt)
.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(indicators?.Select(i => i.Map()).ToArray() ?? []);
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result<Domain.Models.Evidence[]>> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var evidence = await context.Evidences.AsNoTracking()
.OrderByDescending(e => e.CreatedAt)
.Skip((pagination.Page - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync(cancellationToken);
return Result.Ok(evidence?.Select(i => i.Map()).ToArray() ?? []);
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
return Result.Fail($"Project with ID {request.ProjectId} does not exist.");
context.Milestones.Add(new Domain.Entities.Milestone
{
ProjectId = request.ProjectId,
DueAt = request.DueAt,
Name = request.Name,
Priority = request.Priority,
Status = ApprovalStatus.Pending,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
});
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok()
: Result.Fail("Failed to create the milestone");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Users.AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken))
return Result.Fail("Invalid user");
if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken))
return Result.Fail($"Project with ID {request.ProjectId} does not exist.");
context.Indicators.Add(new Domain.Entities.Indicator
{
ProjectId = request.ProjectId,
Name = request.Name,
UnitOfMeasure = request.UnitOfMeasure,
BaselineAmount = request.BaselineAmount,
TargetAmount = request.TargetAmount,
ActualAmount = request.BaselineAmount,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
});
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok()
: Result.Fail("Failed to create the invite");
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -1,9 +0,0 @@
using PostFundManagement.Domain;
namespace PostFundManagement.Application.Evidence;
public record RecordActualRequest(decimal ActualAmount);
public record CreateMilestoneRequest(long ProjectId, DateTime DueAt, string Name, Priority Priority);
public record CreateIndicatorRequest(long ProjectId, string Name, UnitOfMeasure UnitOfMeasure, decimal BaselineAmount, decimal TargetAmount);

Some files were not shown because too many files have changed in this diff Show More