From 2df5b5c2edbe86a8f42075c07c8ad696f57cbcd8 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 1 Sep 2026 11:23:25 +0200 Subject: [PATCH 1/2] Refactored endpoints to be more functional and more expressive --- .editorconfig | 3 + .../GetApplicantInviteEndpoint.cs | 32 +- .../Communications/GetInvitesEndpoint.cs | 31 +- .../Communications/SendInviteEndpoint.cs | 35 ++- .../Evidence/CreateIndicatorEndpoint.cs | 32 +- .../Evidence/CreateMilestoneEndpoint.cs | 31 +- .../Endpoints/Evidence/GetEvidenceEndpoint.cs | 37 +-- .../Evidence/GetEvidenceFilesEndpoint.cs | 29 +- .../Evidence/GetIndicatorEndpoint.cs | 20 +- .../Evidence/GetIndicatorsEndpoint.cs | 27 +- .../Evidence/GetMilestoneEvidenceEndpoint.cs | 33 ++- .../Evidence/GetProjectEvidenceEndpoint.cs | 33 ++- .../Evidence/RecordIndicatorAmountEndpoint.cs | 32 +- .../Evidence/UploadEvidenceFilesEndpoint.cs | 16 +- .../Execution/ApproveDisbursementEndpoint.cs | 34 ++- .../Execution/CreateDisbursementEndpoint.cs | 32 +- .../Endpoints/Execution/CreateRiskEndpoint.cs | 32 +- .../Execution/GetDisbursementEndpoint.cs | 36 +++ .../Execution/GetDisbursementsEndpoint.cs | 28 +- .../Execution/GetProjectRisksEndpoint.cs | 32 -- .../Endpoints/Execution/GetRiskEndpoint.cs | 35 +++ .../Endpoints/Execution/GetRisksEndpoint.cs | 26 +- .../Execution/UpdateDisbursementEndpoint.cs | 33 +++ .../Endpoints/Execution/UpdateRiskEndpoint.cs | 33 +++ .../Governance/CreateOrganisationEndpoint.cs | 30 +- .../Endpoints/Governance/DashboardEndpoint.cs | 27 +- .../Governance/GetInstrumentsEndpoint.cs | 27 +- .../Governance/GetOrganisationByIdEndpoint.cs | 32 +- .../Governance/GetOrganisationsEndpoint.cs | 27 +- .../Governance/GetPortfoliosEndpoint.cs | 27 +- .../Governance/GetProgrammesEndpoint.cs | 27 +- .../Endpoints/Grants/CreateAwardEndpoint.cs | 33 ++- .../Endpoints/Grants/CreateBudgetEndpoint.cs | 22 +- .../Grants/CreateContractEndpoint.cs | 31 +- .../Grants/ExecuteContractEndpoint.cs | 30 +- ...ardByIdEndpoint.cs => GetAwardEndpoint.cs} | 24 +- .../Endpoints/Grants/GetAwardsEndpoint.cs | 15 +- ...etByIdEndpoint.cs => GetBudgetEndpoint.cs} | 22 +- .../Endpoints/Grants/GetBudgetsEndpoint.cs | 15 +- ...ByIdEndpoint.cs => GetContractEndpoint.cs} | 22 +- .../Endpoints/Grants/GetContractsEndpoint.cs | 27 +- .../Projects/CreateActivityEndpoint.cs | 31 +- .../Projects/CreateProjectEndpoint.cs | 33 ++- .../Projects/GetActivitiesEndpoint.cs | 29 +- .../Endpoints/Projects/GetActivityEndpoint.cs | 32 +- .../Projects/GetMilestoneEndpoint.cs | 34 ++- .../Projects/GetMilestonesEndpoint.cs | 29 +- .../Endpoints/Projects/GetProjectEndpoint.cs | 32 +- .../Projects/GetProjectIndicatorsEndpoint.cs | 31 +- .../Projects/GetProjectMilestonesEndpoint.cs | 31 +- .../Projects/GetProjectRisksEndpoint.cs | 35 +++ .../Endpoints/Projects/GetProjectsEndpoint.cs | 27 +- .../Projects/UpdateMilestoneEndpoint.cs | 27 +- .../Projects/UpdateProjectEndpoint.cs | 18 +- .../Projects/UpdateProjectStatusEndpoint.cs | 31 +- .../Endpoints/Users/GetUserEndpoint.cs | 30 +- .../Endpoints/Users/GetUsersEndpoint.cs | 28 +- .../Endpoints/Users/SyncUserEndpoint.cs | 12 +- .../Users/UpdateUserStatusEndpoint.cs | 31 +- .../Abstractions/S3ServiceBase.cs | 54 ++-- .../Extensions/Api.cs | 1 - .../Extensions/General.cs | 2 +- .../Extensions/Hash.cs | 2 +- .../Filters/ActiveUserFilter.cs | 3 +- .../Communications/CommunicationsService.cs | 49 +--- .../Services/Evidence/EvidenceService.cs | 218 +++++--------- .../Services/Execution/ExecutionService.cs | 198 +++++++------ .../Services/Execution/Records.cs | 4 + .../Services/Governance/GovernanceService.cs | 122 +++----- .../Services/Grants/GrantsService.cs | 200 +++++-------- .../Services/Projects/ProjectService.cs | 276 +++++++----------- .../Shared/BrowserLocalStorageService.cs | 66 ++--- .../Services/Shared/ContractS3Service.cs | 4 +- .../Services/Shared/EvidenceS3Service.cs | 4 +- .../Services/Shared/GeneralS3Service.cs | 4 +- .../Services/Shared/HashService.cs | 43 ++- .../Services/Shared/IdentityService.cs | 24 +- .../Services/Shared/TokenService.cs | 45 +-- .../Services/Users/UserService.cs | 74 ++--- .../Abstractions/IS3Service.cs | 4 +- .../Errors/CreateError.cs | 14 + .../Errors/IdDecodeError.cs | 14 + .../Errors/NotFoundError.cs | 14 + .../Errors/UpdateError.cs | 14 + .../Errors/ValidationError.cs | 14 + .../Extensions/Constants.cs | 2 +- 86 files changed, 1565 insertions(+), 1575 deletions(-) create mode 100644 PostFundManagement.Api/Endpoints/Execution/GetDisbursementEndpoint.cs delete mode 100644 PostFundManagement.Api/Endpoints/Execution/GetProjectRisksEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Execution/GetRiskEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Execution/UpdateDisbursementEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Execution/UpdateRiskEndpoint.cs rename PostFundManagement.Api/Endpoints/Grants/{GetAwardByIdEndpoint.cs => GetAwardEndpoint.cs} (53%) rename PostFundManagement.Api/Endpoints/Grants/{GetBudgetByIdEndpoint.cs => GetBudgetEndpoint.cs} (53%) rename PostFundManagement.Api/Endpoints/Grants/{GetContractByIdEndpoint.cs => GetContractEndpoint.cs} (52%) create mode 100644 PostFundManagement.Api/Endpoints/Projects/GetProjectRisksEndpoint.cs create mode 100644 PostFundManagement.Domain/Errors/CreateError.cs create mode 100644 PostFundManagement.Domain/Errors/IdDecodeError.cs create mode 100644 PostFundManagement.Domain/Errors/NotFoundError.cs create mode 100644 PostFundManagement.Domain/Errors/UpdateError.cs create mode 100644 PostFundManagement.Domain/Errors/ValidationError.cs diff --git a/.editorconfig b/.editorconfig index 6ab8a85..2be74d8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -43,6 +43,9 @@ dotnet_diagnostic.MA0004.severity = none [**/*{Enums,Records}.cs] dotnet_diagnostic.MA0048.severity = none +[*.{cs,vb}] +dotnet_diagnostic.IDE0039.severity = none + #### Core EditorConfig Options #### # Indentation and spacing diff --git a/PostFundManagement.Api/Endpoints/Communications/GetApplicantInviteEndpoint.cs b/PostFundManagement.Api/Endpoints/Communications/GetApplicantInviteEndpoint.cs index 132709e..a5c7612 100644 --- a/PostFundManagement.Api/Endpoints/Communications/GetApplicantInviteEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Communications/GetApplicantInviteEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Communications; 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; @@ -14,20 +16,20 @@ public sealed class GetApplicantInviteEndpoint : IEndpoint { builder.MapGet("api/communications/invites/applicant/{userId:Guid}", async (Guid userId, CommunicationsService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.GetApplicantInviteAsync(userId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Gets an applicant's invite") - .WithName(typeof(GetApplicantInviteEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Communications) - .CacheOutput(); + await service.GetApplicantInviteAsync(userId, cancellationToken) switch + { + { IsSuccess: true, Value: var invite } => Results.Ok(invite), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Gets an applicant's invite") + .WithName(typeof(GetApplicantInviteEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Communications) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Communications/GetInvitesEndpoint.cs b/PostFundManagement.Api/Endpoints/Communications/GetInvitesEndpoint.cs index fcda905..59c307f 100644 --- a/PostFundManagement.Api/Endpoints/Communications/GetInvitesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Communications/GetInvitesEndpoint.cs @@ -6,30 +6,29 @@ using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Security; using static PostFundManagement.Application.Extensions.General; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Communications; [ApiVersionTarget(1)] -public class GetInvitesEndpoint : IEndpoint +public sealed class GetInvitesEndpoint : IEndpoint { public void Map(IEndpointRouteBuilder builder) { builder.MapGet("api/communications/invites/{page:int}/{pageSize:int}", async (CommunicationsService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetInvitesAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get a list of sent invitations") - .WithName(typeof(GetInvitesEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Communications) - .CacheOutput(); + await service.GetInvitesAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var invites } => Results.Ok(invites), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of sent invitations") + .WithName(typeof(GetInvitesEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Communications) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Communications/SendInviteEndpoint.cs b/PostFundManagement.Api/Endpoints/Communications/SendInviteEndpoint.cs index 0eab0e2..5ba08c4 100644 --- a/PostFundManagement.Api/Endpoints/Communications/SendInviteEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Communications/SendInviteEndpoint.cs @@ -1,31 +1,34 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Communications; 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 class SendInviteEndpoint : IEndpoint +public sealed class SendInviteEndpoint : IEndpoint { 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) => - { - var result = await service.SendInviteAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Trigger an invite to an organization or candidate (specifying NotificationPlatform)") - .WithName(typeof(SendInviteEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Communications); + await service.SendInviteAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true, Value: var invite } => Results.Ok(invite), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Trigger an invite to an organization or candidate (specifying NotificationPlatform)") + .WithName(typeof(SendInviteEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Communications); } } \ No newline at end of file diff --git a/PostFundManagement.Api/Endpoints/Evidence/CreateIndicatorEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/CreateIndicatorEndpoint.cs index 735d48a..cc641a9 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/CreateIndicatorEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/CreateIndicatorEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,21 @@ public class CreateIndicatorEndpoint : IEndpoint { builder.MapPost("api/evidence/indicators", async (CreateIndicatorRequest request, HttpContext context, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Define indicators and baselines/targets (M08)") - .WithName(typeof(CreateIndicatorEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence); + await service.CreateIndicatorAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Define indicators and baselines/targets (M08)") + .WithName(typeof(CreateIndicatorEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/CreateMilestoneEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/CreateMilestoneEndpoint.cs index fb667d7..14caaf9 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/CreateMilestoneEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/CreateMilestoneEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,20 @@ public class CreateMilestoneEndpoint : IEndpoint { builder.MapPost("api/evidence/milestones", async (CreateMilestoneRequest request, HttpContext context, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Define contractual milestones (M07)") - .WithName(typeof(CreateMilestoneRequest).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence); + await service.CreateMilestoneAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Define contractual milestones (M07)") + .WithName(typeof(CreateMilestoneRequest).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceEndpoint.cs index 39b74e3..df545b4 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -11,23 +13,24 @@ public sealed class GetEvidenceEndpoint : IEndpoint { public void Map(IEndpointRouteBuilder builder) { - builder.MapGet("api/evidence/{evidenceId}", async (string evidenceId, EvidenceService service, + builder.MapGet("api/evidence/{evidenceId}", async (string evidenceId, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetEvidenceAsync(evidenceId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - - }) - .WithDescription("Get a specific evidence record and assiciated file URLs") - .WithName(typeof(GetEvidenceEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence) - .CacheOutput(); + await service.GetEvidenceAsync(evidenceId, cancellationToken) switch + { + { IsSuccess: true, Value: var evidence } => Results.Ok(evidence), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceFilesEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceFilesEndpoint.cs index 42d3a05..f771e10 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceFilesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetEvidenceFilesEndpoint.cs @@ -4,6 +4,7 @@ using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Evidence; @@ -14,20 +15,18 @@ public class GetEvidenceFilesEndpoint : IEndpoint { builder.MapGet("api/evidence/files/{page:int}/{pageSize:int}", async (EvidenceService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetEvidenceFilesAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get a list of all uploaded evidence files metadata") - .WithName(typeof(GetEvidenceFilesEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence) - .CacheOutput(); + await service.GetEvidenceFilesAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var evidences} => Results.Ok(evidences), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of all uploaded evidence files metadata") + .WithName(typeof(GetEvidenceFilesEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Evidence) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorEndpoint.cs index 6b93962..856f65f 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Evidence; 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; @@ -14,19 +16,21 @@ public sealed class GetIndicatorEndpoint : IEndpoint { builder.MapGet("api/evidence/indicators/{indicatorId}", async (string indicatorId, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetIndicatorAsync(indicatorId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) + await service.GetIndicatorAsync(indicatorId, cancellationToken) switch + { + { IsSuccess: true, Value: var indicator } => Results.Ok(indicator), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get a specific indicator.") .WithName(typeof(GetIndicatorEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status500InternalServerError) .WithTags(EndpointTags.Evidence); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorsEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorsEndpoint.cs index a7c2bb5..6e927e8 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetIndicatorsEndpoint.cs @@ -5,6 +5,7 @@ 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.Evidence; @@ -15,19 +16,17 @@ public class GetIndicatorsEndpoint : IEndpoint { builder.MapGet("api/evidence/indicators/{page:int}/{pageSize:int}", async (EvidenceService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetIndicatorsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get a list of indicators") - .WithName(typeof(GetIndicatorsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence); + await service.GetIndicatorsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var indicators } => Results.Ok(indicators), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Get a list of indicators") + .WithName(typeof(GetIndicatorsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Evidence); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetMilestoneEvidenceEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetMilestoneEvidenceEndpoint.cs index 2381905..32d6845 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetMilestoneEvidenceEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetMilestoneEvidenceEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,20 +15,21 @@ public class GetMilestoneEvidenceEndpoint : IEndpoint { builder.MapGet("api/evidence/milestone/{milestoneId}", async (string milestoneId, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetMilestoneEvidencesAsync(milestoneId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get a list of all uploaded evidence metadata for a specific mliestone") - .WithName(typeof(GetMilestoneEvidenceEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence) - .CacheOutput(); + await service.GetMilestoneEvidencesAsync(milestoneId, cancellationToken) switch + { + { IsSuccess: true, Value: var evidences } => Results.Ok(evidences), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/GetProjectEvidenceEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/GetProjectEvidenceEndpoint.cs index 474fe46..9eea96b 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/GetProjectEvidenceEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/GetProjectEvidenceEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,20 +15,21 @@ public class GetProjectEvidenceEndpoint : IEndpoint { builder.MapGet("api/evidence/milestone/{projectId}", async (string projectId, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetProjectEvidencesAsync(projectId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get a list of all uploaded evidence metadata for a specific project") - .WithName(typeof(GetProjectEvidenceEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence) - .CacheOutput(); + await service.GetProjectEvidencesAsync(projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var evidence } => Results.Ok(evidence), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/RecordIndicatorAmountEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/RecordIndicatorAmountEndpoint.cs index f47b467..bb901ea 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/RecordIndicatorAmountEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/RecordIndicatorAmountEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,21 @@ public class RecordIndicatorAmountEndpoint : IEndpoint { builder.MapPost("api/evidence/indicators/{indicatorId}/actuals/{actualAmount:decimal}", async (string indicatorId, decimal actualAmount, HttpContext context, EvidenceService service, CancellationToken cancellationToken = default) => - { - var result = await service.RecordIndicatorActualAmountAsync(indicatorId, actualAmount, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Record actual values for indicators (M08)") - .WithName(typeof(RecordIndicatorAmountEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Evidence); + await service.RecordIndicatorActualAmountAsync(indicatorId, actualAmount, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Record actual values for indicators (M08)") + .WithName(typeof(RecordIndicatorAmountEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence); } } diff --git a/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs index 291be95..a5dd1e8 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -25,11 +27,12 @@ public class UploadEvidenceFilesEndpoint : IEndpoint if (file == null || file.Length == 0) return Results.BadRequest("No file uploaded or file is empty."); - var result = await service.UploadEvidenceFileAsync(userId, form, file, cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); + return await service.UploadEvidenceFileAsync(userId, form, file, cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }; }) .RequireAuthorization() .DisableAntiforgery() @@ -37,7 +40,8 @@ public class UploadEvidenceFilesEndpoint : IEndpoint .WithName(typeof(UploadEvidenceFilesEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) .Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Evidence); } diff --git a/PostFundManagement.Api/Endpoints/Execution/ApproveDisbursementEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/ApproveDisbursementEndpoint.cs index a66d77f..f8611dd 100644 --- a/PostFundManagement.Api/Endpoints/Execution/ApproveDisbursementEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Execution/ApproveDisbursementEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -11,21 +13,23 @@ public class ApproveDisbursementEndpoint : IEndpoint { 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) => - { - var result = await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Approve disbursement and release payment (M06)") - .WithName(typeof(ApproveDisbursementEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); + await service.ApproveDisbursementAsync(disbursementId, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Approve disbursement and release payment (M06)") + .WithName(typeof(ApproveDisbursementEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); } } diff --git a/PostFundManagement.Api/Endpoints/Execution/CreateDisbursementEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/CreateDisbursementEndpoint.cs index 4e95fcb..31536e0 100644 --- a/PostFundManagement.Api/Endpoints/Execution/CreateDisbursementEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Execution/CreateDisbursementEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,21 @@ public class CreateDisbursementEndpoint : IEndpoint { builder.MapPost("api/execution/disbursements", async (CreateDisbursementRequest request, ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Create a new disbursement schedule (M06)") - .WithName(typeof(CreateDisbursementEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); + await service.CreateDisbursementAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Create a new disbursement schedule (M06)") + .WithName(typeof(CreateDisbursementEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); } } diff --git a/PostFundManagement.Api/Endpoints/Execution/CreateRiskEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/CreateRiskEndpoint.cs index c10a61a..14d5eba 100644 --- a/PostFundManagement.Api/Endpoints/Execution/CreateRiskEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Execution/CreateRiskEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,21 @@ public class CreateRiskEndpoint : IEndpoint { builder.MapPost("api/execution/risks", async (CreateRiskRequest request, ExecutionService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Add risk to risk register (M09)") - .WithName(typeof(CreateRiskEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); + await service.CreateRiskAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Add risk to risk register (M09)") + .WithName(typeof(CreateRiskEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); } } diff --git a/PostFundManagement.Api/Endpoints/Execution/GetDisbursementEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/GetDisbursementEndpoint.cs new file mode 100644 index 0000000..f8d3acb --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Execution/GetDisbursementEndpoint.cs @@ -0,0 +1,36 @@ +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Execution; +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, ExecutionService service, + CancellationToken cancellationToken = default) => + await service.GetDisbursementAsync(disbursementIdHash: disbursementId, cancellationToken) switch + { + { IsSuccess: true, Value: var disbursement } => Results.Ok(disbursement), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a specific disbursement") + .WithName(typeof(GetDisbursementEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); + } +} diff --git a/PostFundManagement.Api/Endpoints/Execution/GetDisbursementsEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/GetDisbursementsEndpoint.cs index 055d62c..3b55a5c 100644 --- a/PostFundManagement.Api/Endpoints/Execution/GetDisbursementsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Execution/GetDisbursementsEndpoint.cs @@ -5,6 +5,7 @@ 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.Execution; @@ -15,19 +16,18 @@ public class GetDisbursementsEndpoint : IEndpoint { builder.MapGet("api/execution/disbursements", async (ExecutionService service, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) => - { - var result = await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get a list of disbursements") - .WithName(typeof(GetDisbursementsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); + await service.GetDisbursementsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var disbursements } => Results.Ok(disbursements), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of disbursements") + .WithName(typeof(GetDisbursementsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); } } diff --git a/PostFundManagement.Api/Endpoints/Execution/GetProjectRisksEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/GetProjectRisksEndpoint.cs deleted file mode 100644 index e452fd6..0000000 --- a/PostFundManagement.Api/Endpoints/Execution/GetProjectRisksEndpoint.cs +++ /dev/null @@ -1,32 +0,0 @@ -using PostFundManagement.Application.Extensions; -using PostFundManagement.Application.Services.Execution; -using PostFundManagement.Domain.Abstractions; -using PostFundManagement.Domain.Extensions; -using PostFundManagement.Domain.Models; -using PostFundManagement.Domain.Security; - -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(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); - } -} diff --git a/PostFundManagement.Api/Endpoints/Execution/GetRiskEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/GetRiskEndpoint.cs new file mode 100644 index 0000000..65d12ce --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Execution/GetRiskEndpoint.cs @@ -0,0 +1,35 @@ +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Execution; +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, ExecutionService service, + CancellationToken cancellationToken = default) => + await service.GetRiskAsync(riskIdHash: riskId, cancellationToken) switch + { + { IsSuccess: true, Value: var risk } => Results.Ok(risk), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a specific risk") + .WithName(typeof(GetRiskEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); + } +} diff --git a/PostFundManagement.Api/Endpoints/Execution/GetRisksEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/GetRisksEndpoint.cs index e4afa7d..a1814c9 100644 --- a/PostFundManagement.Api/Endpoints/Execution/GetRisksEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Execution/GetRisksEndpoint.cs @@ -4,6 +4,7 @@ using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Execution; @@ -14,18 +15,17 @@ public class GetRisksEndpoint : IEndpoint { builder.MapGet("api/execution/risks", async (ExecutionService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get a list of all risks") - .WithName(typeof(GetRisksEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Execution); + await service.GetRisksAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var risks } => Results.Ok(risks), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of all risks") + .WithName(typeof(GetRisksEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); } } diff --git a/PostFundManagement.Api/Endpoints/Execution/UpdateDisbursementEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/UpdateDisbursementEndpoint.cs new file mode 100644 index 0000000..d188977 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Execution/UpdateDisbursementEndpoint.cs @@ -0,0 +1,33 @@ +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, + ExecutionService service, CancellationToken cancellationToken = default) => + await service.UpdateDisbursementAsync(request, userId: context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Execution); + } +} diff --git a/PostFundManagement.Api/Endpoints/Execution/UpdateRiskEndpoint.cs b/PostFundManagement.Api/Endpoints/Execution/UpdateRiskEndpoint.cs new file mode 100644 index 0000000..de72b1c --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Execution/UpdateRiskEndpoint.cs @@ -0,0 +1,33 @@ +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, + ExecutionService service, CancellationToken cancellationToken = default) => + await service.UpdateRiskAsync(request, cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Execution); + } +} diff --git a/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs index cc581f8..5b50c9d 100644 --- a/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,19 @@ public class CreateOrganisationEndpoint : IEndpoint { builder.MapPost("api/governance/organisations", async (CreateOrganisationRequest request, HttpContext context, GovernanceService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Create a new organization profile") - .WithName(typeof(CreateOrganisationEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.CreateOrganisationAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Create a new organization profile") + .WithName(typeof(CreateOrganisationEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/DashboardEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/DashboardEndpoint.cs index e293b2a..76397a4 100644 --- a/PostFundManagement.Api/Endpoints/Governance/DashboardEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/DashboardEndpoint.cs @@ -3,6 +3,7 @@ using PostFundManagement.Application.Services.Governance; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Governance; @@ -13,19 +14,17 @@ public class DashboardEndpoint : IEndpoint { builder.MapGet("api/governance/dashboard", async (GovernanceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetDashboardAsync(cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get executive portfolio dashboard metrics and KPIs") - .WithName(typeof(DashboardEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetDashboardAsync(cancellationToken) switch + { + { IsSuccess: true, Value: var dashboard } => Results.Ok(dashboard), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get executive portfolio dashboard metrics and KPIs") + .WithName(typeof(DashboardEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/GetInstrumentsEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetInstrumentsEndpoint.cs index cdb8212..2a75b08 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetInstrumentsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetInstrumentsEndpoint.cs @@ -4,6 +4,7 @@ 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.Governance; @@ -14,19 +15,17 @@ public class GetInstrumentsEndpoint : IEndpoint { builder.MapGet("api/governance/instruments", async (GovernanceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetInstrumentsAsync(cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get all funding instruments") - .WithName(typeof(GetInstrumentsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetInstrumentsAsync(cancellationToken) switch + { + { IsSuccess: true, Value: var instrumenrs } => Results.Ok(instrumenrs), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Get all funding instruments") + .WithName(typeof(GetInstrumentsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs index a869d75..9fba496 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Governance; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Governance; @@ -14,19 +16,21 @@ public class GetOrganisationByIdEndpoint : IEndpoint { 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(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetOrganisationByIdAsync(organisationId, cancellationToken) switch + { + { IsSuccess: true, Value: var organisation } => Results.Ok(organisationId), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get organization profile by ID") + .WithName(typeof(GetOrganisationByIdEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationsEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationsEndpoint.cs index 1b3bd87..6042f3d 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationsEndpoint.cs @@ -5,6 +5,7 @@ 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.Governance; @@ -15,19 +16,17 @@ public class GetOrganisationsEndpoint : IEndpoint { builder.MapGet("api/governance/organisations", async (GovernanceService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get a paginated directory of organizations") - .WithName(typeof(GetOrganisationsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetOrganisationsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var orgnanisations } => Results.Ok(orgnanisations), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a paginated directory of organizations") + .WithName(typeof(GetOrganisationsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/GetPortfoliosEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetPortfoliosEndpoint.cs index 4539d37..a9fe24c 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetPortfoliosEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetPortfoliosEndpoint.cs @@ -4,6 +4,7 @@ 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.Governance; @@ -14,19 +15,17 @@ public class GetPortfoliosEndpoint : IEndpoint { builder.MapGet("api/governance/portfolios", async (GovernanceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetPorfoliosAsync(cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get all portfolios") - .WithName(typeof(GetPortfoliosEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetPorfoliosAsync(cancellationToken) switch + { + { IsSuccess: true, Value: var portfolios } => Results.Ok(portfolios), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get all portfolios") + .WithName(typeof(GetPortfoliosEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Governance/GetProgrammesEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetProgrammesEndpoint.cs index c6fddea..82c8cb5 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetProgrammesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetProgrammesEndpoint.cs @@ -4,6 +4,7 @@ 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.Governance; @@ -14,19 +15,17 @@ public class GetProgrammesEndpoint : IEndpoint { builder.MapGet("api/governance/programmes", async (GovernanceService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetProgrammesAsync(cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get all programmes") - .WithName(typeof(GetProgrammesEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Governance); + await service.GetProgrammesAsync(cancellationToken) switch + { + { IsSuccess: true, Value: var programmes } => Results.Ok(programmes), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get all programmes") + .WithName(typeof(GetProgrammesEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Governance); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/CreateAwardEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/CreateAwardEndpoint.cs index 4d424cc..0cbd807 100644 --- a/PostFundManagement.Api/Endpoints/Grants/CreateAwardEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/CreateAwardEndpoint.cs @@ -1,8 +1,10 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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.Grants; @@ -13,19 +15,22 @@ public class CreateAwardEndpoint : IEndpoint { builder.MapPost("api/grants/awards", async (CreateAwardRequest request, HttpContext context, GrantsService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Register an approved funding award (M01)") - .WithName(typeof(CreateAwardEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Grants); + await service.CreateAwardAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Register an approved funding award (M01)") + .WithName(typeof(CreateAwardEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/CreateBudgetEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/CreateBudgetEndpoint.cs index cbf7f9c..0a9c8f2 100644 --- a/PostFundManagement.Api/Endpoints/Grants/CreateBudgetEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/CreateBudgetEndpoint.cs @@ -1,8 +1,10 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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.Grants; @@ -13,18 +15,22 @@ public class CreateBudgetEndpoint : IEndpoint { builder.MapPost("api/grants/budgets", async (CreateBudgetRequest request, HttpContext context, GrantsService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) + await service.CreateBudgetAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Create a new budget line (M05)") .WithName(typeof(CreateBudgetEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) .Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Grants); } diff --git a/PostFundManagement.Api/Endpoints/Grants/CreateContractEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/CreateContractEndpoint.cs index 8219ce7..4438757 100644 --- a/PostFundManagement.Api/Endpoints/Grants/CreateContractEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/CreateContractEndpoint.cs @@ -1,8 +1,10 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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.Grants; @@ -13,19 +15,20 @@ public class CreateContractEndpoint : IEndpoint { builder.MapPost("api/grants/contracts", async (CreateContractRequest request, HttpContext context, GrantsService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateContractAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Create a contract record from an approved award (M02)") - .WithName(typeof(CreateContractEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Grants); + await service.CreateContractAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Create a contract record from an approved award (M02)") + .WithName(typeof(CreateContractEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/ExecuteContractEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/ExecuteContractEndpoint.cs index 415c44b..1d95635 100644 --- a/PostFundManagement.Api/Endpoints/Grants/ExecuteContractEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/ExecuteContractEndpoint.cs @@ -1,8 +1,10 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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.Grants; @@ -13,19 +15,19 @@ public class ExecuteContractEndpoint : IEndpoint { builder.MapPost("api/grants/contracts/execute", async (ExecuteContractRequest request, HttpContext context, GrantsService service, CancellationToken cancellationToken = default) => - { - var result = await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Execute the contract and lock the contractual version") - .WithName(typeof(ExecuteContractEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Grants); + await service.ExecuteContractAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Execute the contract and lock the contractual version") + .WithName(typeof(ExecuteContractEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetAwardByIdEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetAwardEndpoint.cs similarity index 53% rename from PostFundManagement.Api/Endpoints/Grants/GetAwardByIdEndpoint.cs rename to PostFundManagement.Api/Endpoints/Grants/GetAwardEndpoint.cs index 2a3cfd3..ff79c77 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetAwardByIdEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetAwardEndpoint.cs @@ -1,32 +1,36 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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 GetAwardByIdEndpoint : IEndpoint +public class GetAwardEndpoint : 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(); - }) + await service.GetAwardAsync(awardId, cancellationToken) switch + { + { IsSuccess: true, Value: var award } => Results.Ok(award), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get award by ID") - .WithName(typeof(GetAwardByIdEndpoint).ToEndpointName()) + .WithName(typeof(GetAwardEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status500InternalServerError) .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetAwardsEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetAwardsEndpoint.cs index 1c662ba..960d828 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetAwardsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetAwardsEndpoint.cs @@ -5,6 +5,7 @@ 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.Grants; @@ -15,18 +16,16 @@ public class GetAwardsEndpoint : IEndpoint { builder.MapGet("api/grants/awards", async (GrantsService service, int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) => - { - var result = await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) + await service.GetAwardsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var awards } => Results.Ok(awards), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get a list of awards") .WithName(typeof(GetAwardsEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) .Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Grants); } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetBudgetByIdEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetBudgetEndpoint.cs similarity index 53% rename from PostFundManagement.Api/Endpoints/Grants/GetBudgetByIdEndpoint.cs rename to PostFundManagement.Api/Endpoints/Grants/GetBudgetEndpoint.cs index a856583..501cfea 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetBudgetByIdEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetBudgetEndpoint.cs @@ -1,32 +1,36 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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 GetBudgetByIdEndpoint : IEndpoint +public class GetBudgetEndpoint : 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(); - }) + await service.GetBudgetAsync(budgetId, cancellationToken) switch + { + { IsSuccess: true, Value: var budget } => Results.Ok(budget), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get budget by ID") - .WithName(typeof(GetBudgetByIdEndpoint).ToEndpointName()) + .WithName(typeof(GetBudgetEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetBudgetsEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetBudgetsEndpoint.cs index 6a14b34..4118bb5 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetBudgetsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetBudgetsEndpoint.cs @@ -5,6 +5,7 @@ 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.Grants; @@ -15,18 +16,16 @@ public class GetBudgetsEndpoint : IEndpoint { builder.MapGet("api/grants/budgets", async (GrantsService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) + await service.GetBudgetsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var budgets } => Results.Ok(budgets), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get all budgets") .WithName(typeof(GetBudgetsEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) .Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Grants); } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetContractByIdEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetContractEndpoint.cs similarity index 52% rename from PostFundManagement.Api/Endpoints/Grants/GetContractByIdEndpoint.cs rename to PostFundManagement.Api/Endpoints/Grants/GetContractEndpoint.cs index c8ce7e8..51c0d8f 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetContractByIdEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetContractEndpoint.cs @@ -1,32 +1,36 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Grants; 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 GetContractByIdEndpoint : IEndpoint +public class GetContractEndpoint : 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(); - }) + await service.GetContractAsync(contractId, cancellationToken) switch + { + { IsSuccess: true, Value: var contract } => Results.Ok(contract), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Get contract by ID") - .WithName(typeof(GetContractByIdEndpoint).ToEndpointName()) + .WithName(typeof(GetContractEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Grants/GetContractsEndpoint.cs b/PostFundManagement.Api/Endpoints/Grants/GetContractsEndpoint.cs index e962e8a..35b980d 100644 --- a/PostFundManagement.Api/Endpoints/Grants/GetContractsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Grants/GetContractsEndpoint.cs @@ -5,6 +5,7 @@ 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.Grants; @@ -15,19 +16,17 @@ public class GetContractsEndpoint : IEndpoint { builder.MapGet("api/grants/contracts", async (GrantsService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get all contracts") - .WithName(typeof(GetContractsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Grants); + await service.GetContractsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var contracts } => Results.Ok(contracts), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get all contracts") + .WithName(typeof(GetContractsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Grants); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/CreateActivityEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/CreateActivityEndpoint.cs index ab01dee..e9bcac1 100644 --- a/PostFundManagement.Api/Endpoints/Projects/CreateActivityEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/CreateActivityEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,20 @@ public class CreateActivityEndpoint : IEndpoint { builder.MapPost("api/projects/activities", async (CreateActivityRequest request, HttpContext context, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Add a new activity under project (M04)") - .WithName(typeof(CreateActivityEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.CreateActivityAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } result => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Add a new activity under project (M04)") + .WithName(typeof(CreateActivityEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/CreateProjectEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/CreateProjectEndpoint.cs index ad7fb88..6636b2a 100644 --- a/PostFundManagement.Api/Endpoints/Projects/CreateProjectEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/CreateProjectEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,22 @@ public class CreateProjectEndpoint : IEndpoint { builder.MapPost("api/projects", async (CreateProjectRequest request, HttpContext context, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Create a new project (M04)") - .WithName(typeof(CreateProjectEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.CreateProjectAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } result => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Create a new project (M04)") + .WithName(typeof(CreateProjectEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetActivitiesEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetActivitiesEndpoint.cs index 0030ffa..571b7a8 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetActivitiesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetActivitiesEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,18 +15,19 @@ public class GetActivitiesEndpoint : IEndpoint { builder.MapGet("api/projects/{projectId}/activities", async (string projectId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetActivitiesAsync(projectId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get a list of activities for a project") - .WithName(typeof(GetActivitiesEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetActivitiesAsync(projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var activities } => Results.Ok(activities), + var r when r.HasError() => Results.Forbid(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of activities for a project") + .WithName(typeof(GetActivitiesEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetActivityEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetActivityEndpoint.cs index ebb83b1..bf1299c 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetActivityEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetActivityEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,21 @@ public class GetActivityEndpoint : IEndpoint { builder.MapGet("api/projects/activities/{activityId}", async (string activityId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetActivityAsync(activityId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Gets a specific activity by it's id") - .WithName(typeof(GetActivityEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetActivityAsync(activityId, cancellationToken) switch + { + { IsSuccess: true, Value: var activity } => Results.Ok(activity), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetMilestoneEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetMilestoneEndpoint.cs index 3cc0672..317c5db 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetMilestoneEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetMilestoneEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Projects; 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; @@ -14,20 +16,22 @@ public class GetMilestoneEndpoint : IEndpoint { builder.MapGet("api/projects/milestones/{milestoneId}", async (string milestoneId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetMilestoneAsync(milestoneId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Gets a specific milestone") - .WithName(typeof(GetMilestoneEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects) - .CacheOutput(); + await service.GetMilestoneAsync(milestoneId, cancellationToken) switch + { + { IsSuccess: true, Value: var milestone } => Results.Ok(milestone), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Gets a specific milestone") + .WithName(typeof(GetMilestoneEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects) + .CacheOutput(); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs index d4ee1af..70bc4aa 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs @@ -5,6 +5,7 @@ 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; @@ -15,20 +16,18 @@ public class GetMilestonesEndpoint : IEndpoint { builder.MapGet("api/projects/milestones/{page:int}/{pageSize:int}", async (ProjectService service, int page = 1, int pageSize = 100, 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(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetMilestonesAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var milestones } => Results.Ok(milestones), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .RequireAuthorization() + .WithDescription("Get a list of all milestones") + .WithName(typeof(GetMilestonesEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProjectEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProjectEndpoint.cs index e9327d1..f8e0e56 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetProjectEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetProjectEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Projects; 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; @@ -14,19 +16,21 @@ public class GetProjectEndpoint : IEndpoint { builder.MapGet("api/projects/{projectId}", async (string projectId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetProjectAsync(projectId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get project by ID") - .WithName(typeof(GetProjectEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetProjectAsync(projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var project } => Results.Ok(project), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => Results.NotFound(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get project by ID") + .WithName(typeof(GetProjectEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProjectIndicatorsEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProjectIndicatorsEndpoint.cs index 6a2dd56..4c3605f 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetProjectIndicatorsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetProjectIndicatorsEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Projects; 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; @@ -14,19 +16,20 @@ public class GetProjectIndicatorsEndpoint : IEndpoint { builder.MapGet("api/projects/indicators/project/{projectId}", async (string projectId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Get get a detailed list of project indicators") - .WithName(typeof(GetProjectIndicatorsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetProjectIndicatorsAsync(projectIdHash: projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var indicators } => Results.Ok(indicators), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProjectMilestonesEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProjectMilestonesEndpoint.cs index 3a48bcc..3efcdac 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetProjectMilestonesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetProjectMilestonesEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Projects; 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; @@ -14,19 +16,20 @@ public class GetProjectMilestonesEndpoint : IEndpoint { builder.MapGet("api/projects/{projectId}/milestones", async (string projectId, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.GetProjectMilestonesAsync(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(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetProjectMilestonesAsync(projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var milestones } => Results.Ok(milestones), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProjectRisksEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProjectRisksEndpoint.cs new file mode 100644 index 0000000..8b1a8b0 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/GetProjectRisksEndpoint.cs @@ -0,0 +1,35 @@ +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Projects; +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, + ProjectService service, CancellationToken cancellationToken = default) => + await service.GetProjectRisksAsync(projectId, cancellationToken) switch + { + { IsSuccess: true, Value: var risks } => Results.Ok(risks), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); + } +} diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProjectsEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProjectsEndpoint.cs index a6aa318..f421d4b 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetProjectsEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetProjectsEndpoint.cs @@ -5,6 +5,7 @@ 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; @@ -15,19 +16,17 @@ public class GetProjectsEndpoint : IEndpoint { builder.MapGet("api/projects", async (ProjectService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Get a list of projects") - .WithName(typeof(GetProjectsEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.GetProjectsAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var projects } => Results.Ok(projects), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of projects") + .WithName(typeof(GetProjectsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/UpdateMilestoneEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/UpdateMilestoneEndpoint.cs index cf5712e..584e4f2 100644 --- a/PostFundManagement.Api/Endpoints/Projects/UpdateMilestoneEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/UpdateMilestoneEndpoint.cs @@ -3,6 +3,7 @@ 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; @@ -13,19 +14,17 @@ public sealed class UpdateMilestoneEndpoint : IEndpoint { builder.MapPut("api/projects/milestone", async (UpdateMilestoneRequest request, ProjectService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.UpdateMilestoneAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Update project milestone details") - .WithName(typeof(UpdateMilestoneEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + 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(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Projects/UpdateProjectEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/UpdateProjectEndpoint.cs index d5a5157..4b3e640 100644 --- a/PostFundManagement.Api/Endpoints/Projects/UpdateProjectEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/UpdateProjectEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,18 +15,18 @@ public sealed class UpdateProjectEndpoint : IEndpoint { builder.MapPut("api/projects", async (UpdateProjectRequest request, ProjectService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.UpdateProjectAsync(request, userId: context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) + await service.UpdateProjectAsync(request, userId: context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) .WithDescription("Update project details") .WithName(typeof(UpdateProjectEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) .Produces(StatusCodes.Status401Unauthorized) .WithTags(EndpointTags.Projects); } diff --git a/PostFundManagement.Api/Endpoints/Projects/UpdateProjectStatusEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/UpdateProjectStatusEndpoint.cs index bfaefd6..1a15510 100644 --- a/PostFundManagement.Api/Endpoints/Projects/UpdateProjectStatusEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/UpdateProjectStatusEndpoint.cs @@ -1,8 +1,10 @@ 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; @@ -13,19 +15,20 @@ public class UpdateProjectStatusEndpoint : IEndpoint { builder.MapPut("api/projects/status", async (UpdateProjectStatusRequest request, HttpContext context, ProjectService service, CancellationToken cancellationToken = default) => - { - var result = await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); - }) - .WithDescription("Update project status (M04)") - .WithName(typeof(UpdateProjectStatusEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status400BadRequest) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Projects); + await service.UpdateProjectStatusAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); } } diff --git a/PostFundManagement.Api/Endpoints/Users/GetUserEndpoint.cs b/PostFundManagement.Api/Endpoints/Users/GetUserEndpoint.cs index c2ec515..ccd860f 100644 --- a/PostFundManagement.Api/Endpoints/Users/GetUserEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Users/GetUserEndpoint.cs @@ -1,9 +1,11 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Users; 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; @@ -14,19 +16,19 @@ public sealed class GetUserEndpoint : IEndpoint { builder.MapGet("api/users/{userId:Guid}", async (Guid userId, UserService service, HttpContext context, CancellationToken cancellationToken = default) => - { - var result = await service.GetUserAsync(userId, cancellationToken); - - return result.IsSuccess - ? Results.Ok(result.Value) - : Results.NotFound(); - }) - .WithDescription("Gets the metadata of a single user.") - .WithName(typeof(GetUserEndpoint).ToEndpointName()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Users); + await service.GetUserAsync(userId, cancellationToken) switch + { + { IsSuccess: true, Value: var user } => Results.Ok(user), + var r when r.HasError() => 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(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Users); } } diff --git a/PostFundManagement.Api/Endpoints/Users/GetUsersEndpoint.cs b/PostFundManagement.Api/Endpoints/Users/GetUsersEndpoint.cs index f260647..aba25c5 100644 --- a/PostFundManagement.Api/Endpoints/Users/GetUsersEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Users/GetUsersEndpoint.cs @@ -5,6 +5,7 @@ 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.Users; @@ -15,19 +16,18 @@ public class GetUsersEndpoint : IEndpoint { builder.MapPost("api/users/{page:int}/{pageSize:int}", async (UserFilterRequest request, UserService service, int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => - { - var result = await service.GetUsersAsync(Pagination.Create(page, pageSize), request, cancellationToken); - - return result.IsSuccess - ? 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()) - .MapToApiVersion(new ApiVersion(1)) - .Produces(StatusCodes.Status200OK) - .Produces(StatusCodes.Status404NotFound) - .Produces(StatusCodes.Status401Unauthorized) - .WithTags(EndpointTags.Users); + await service.GetUsersAsync(Pagination.Create(page, pageSize), request, cancellationToken) switch + { + { IsSuccess: true, Value: var users } => Results.Ok(users), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Paginated, searchable directory of system users. Supports filtering by ActivityStatus, searching by Email, and sorting") + .WithName(typeof(GetUsersEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Users); } } \ No newline at end of file diff --git a/PostFundManagement.Api/Endpoints/Users/SyncUserEndpoint.cs b/PostFundManagement.Api/Endpoints/Users/SyncUserEndpoint.cs index cc0efa1..1a0b93e 100644 --- a/PostFundManagement.Api/Endpoints/Users/SyncUserEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Users/SyncUserEndpoint.cs @@ -2,8 +2,10 @@ using PostFundManagement.Application.Extensions; using PostFundManagement.Application.Services.Communications.Events; using PostFundManagement.Application.Services.Users; 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; @@ -20,15 +22,19 @@ public class SyncUserEndpoint : IEndpoint if (result.IsSuccess) await jobOrchestrator.SendAsync(SyncUserEvent.Create(), cancellationToken); - return result.IsSuccess - ? Results.Ok() - : Results.BadRequest(result.Errors.ToProblems()); + return result switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }; }) .WithDescription("Trigger integration and data synchronization in the background") .WithName(typeof(SyncUserEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) .WithTags(EndpointTags.Users); } } diff --git a/PostFundManagement.Api/Endpoints/Users/UpdateUserStatusEndpoint.cs b/PostFundManagement.Api/Endpoints/Users/UpdateUserStatusEndpoint.cs index 4eee9d1..38b9b21 100644 --- a/PostFundManagement.Api/Endpoints/Users/UpdateUserStatusEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Users/UpdateUserStatusEndpoint.cs @@ -2,8 +2,10 @@ using PostFundManagement.Application.Services.Users; 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; @@ -14,19 +16,20 @@ public sealed class UpdateUserStatusEndpoint : IEndpoint { builder.MapGet("api/users/{userId:Guid}/status/{status:int}", async (Guid userId, ActivityStatus status, UserService service, CancellationToken cancellationToken = default) => - { - var result = await service.UpdateUserStatusAsync(userId, status, cancellationToken); - - return result.IsSuccess - ? Results.Ok() - : Results.NotFound(); - }) - .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) - .WithTags(EndpointTags.Users); + await service.UpdateUserStatusAsync(userId, status, cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Users); } } diff --git a/PostFundManagement.Application/Abstractions/S3ServiceBase.cs b/PostFundManagement.Application/Abstractions/S3ServiceBase.cs index 8741104..1cb715a 100644 --- a/PostFundManagement.Application/Abstractions/S3ServiceBase.cs +++ b/PostFundManagement.Application/Abstractions/S3ServiceBase.cs @@ -1,4 +1,5 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; +using PostFundManagement.Domain.Errors; namespace PostFundManagement.Application.Abstractions; @@ -9,17 +10,16 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3) protected abstract string BucketName { get; } protected abstract string CdnBaseUrl { get; } - public virtual async Task> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default) - { - try + public virtual async ValueTask> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { if (string.IsNullOrWhiteSpace(BucketName)) - return Result.Fail("Bucket name is not configured."); + return Result.Fail(Domain.Errors.ValidationError.Create("Bucket name is not configured.")); if (string.IsNullOrWhiteSpace(CdnBaseUrl)) - return Result.Fail("CDN base URL is not configured."); + return Result.Fail(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.DisposeAsync(); @@ -28,8 +28,8 @@ public abstract class S3ServiceBase(IAmazonS3 amazonS3) var fileHash = HashService.StreamToSha256Hash(stream); - if(string.IsNullOrWhiteSpace(fileHash)) - return Result.Fail("Failed to compute file hash."); + if (string.IsNullOrWhiteSpace(fileHash)) + return Result.Fail(CreateError.Create("Failed to compute file hash.")); 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); - return response.HttpStatusCode != System.Net.HttpStatusCode.OK - ? Result.Fail($"Failed to upload {fileName} to S3.") + return response.HttpStatusCode != HttpStatusCode.OK + ? Result.Fail(CreateError.Create($"Failed to upload {fileName} to S3.")) : Result.Ok($"{CdnBaseUrl}/{fileKey}"); - } - catch (Exception ex) - { - return Result.Fail(new Error($"Error uploading {fileName} to S3: {ex.Message}").CausedBy(ex)); - } - } + })); - public virtual async Task DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) - { - try + public virtual async ValueTask DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { 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, Key = fileKey - }; + }, cancellationToken); - var response = await Client.DeleteObjectAsync(deleteRequest, cancellationToken); - - 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)); - } - } + return Result.FailIf(response.HttpStatusCode == HttpStatusCode.NoContent, + UpdateError.Create($"Failed to delete {fileKey} from S3.")); + })); } \ No newline at end of file diff --git a/PostFundManagement.Application/Extensions/Api.cs b/PostFundManagement.Application/Extensions/Api.cs index b093b5a..db3c0a8 100644 --- a/PostFundManagement.Application/Extensions/Api.cs +++ b/PostFundManagement.Application/Extensions/Api.cs @@ -1,4 +1,3 @@ -using Asp.Versioning; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Security; using PostFundManagement.Domain.Security.Configuration; diff --git a/PostFundManagement.Application/Extensions/General.cs b/PostFundManagement.Application/Extensions/General.cs index a9969f4..2c598bc 100644 --- a/PostFundManagement.Application/Extensions/General.cs +++ b/PostFundManagement.Application/Extensions/General.cs @@ -19,7 +19,7 @@ public static class General public static Guid GetUserId(this HttpContext context) => (Guid)context.Items[Domain.Extensions.Constants.UserIdItemKey]!; - public static string GetUserEmail(this HttpContext context) => (string)context.Items[Domain.Extensions.Constants.EmaiItemKey]!; + public static string GetUserEmail(this HttpContext context) => (string)context.Items[Domain.Extensions.Constants.EmailtemKey]!; public static (bool Decoded, Guid userId) GetUserId(this ClaimsPrincipal principal) { diff --git a/PostFundManagement.Application/Extensions/Hash.cs b/PostFundManagement.Application/Extensions/Hash.cs index 70592b5..b244a1e 100644 --- a/PostFundManagement.Application/Extensions/Hash.cs +++ b/PostFundManagement.Application/Extensions/Hash.cs @@ -1,4 +1,4 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain.Configuration.Hash; namespace PostFundManagement.Application.Extensions; diff --git a/PostFundManagement.Application/Filters/ActiveUserFilter.cs b/PostFundManagement.Application/Filters/ActiveUserFilter.cs index da1bf9a..c524585 100644 --- a/PostFundManagement.Application/Filters/ActiveUserFilter.cs +++ b/PostFundManagement.Application/Filters/ActiveUserFilter.cs @@ -26,9 +26,8 @@ public sealed class ActiveUserFilter(IdentityService identityService) : IEndpoin return (object?)Results.Json(new { message = "Account approval is pending or inactive." }, statusCode: StatusCodes.Status403Forbidden); context.HttpContext.Items[UserIdItemKey] = userId; - context.HttpContext.Items[EmaiItemKey] = email; + context.HttpContext.Items[EmailtemKey] = email; return await next(context); - } } diff --git a/PostFundManagement.Application/Services/Communications/CommunicationsService.cs b/PostFundManagement.Application/Services/Communications/CommunicationsService.cs index 1ab6761..93476fa 100644 --- a/PostFundManagement.Application/Services/Communications/CommunicationsService.cs +++ b/PostFundManagement.Application/Services/Communications/CommunicationsService.cs @@ -1,5 +1,6 @@ using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -8,11 +9,10 @@ namespace PostFundManagement.Application.Services.Communications; public sealed class CommunicationsService(IDbContextFactory contextFactory) : IService { - public async ValueTask> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask> SendInviteAsync(InviteRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var invite = new Domain.Entities.Invite { @@ -40,19 +40,13 @@ public sealed class CommunicationsService(IDbContextFactory 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)); - } - } + : Result.Fail(CreateError.New); + })); - public async ValueTask> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetInvitesAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var invites = await context.Invites.AsNoTracking() .OrderByDescending(i => i.CreatedAt) @@ -60,29 +54,18 @@ public sealed class CommunicationsService(IDbContextFactory i.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(invites.Select(i => i.Map()).ToArray()); + })); - public async ValueTask> GetApplicantInviteAsync(Guid userId, CancellationToken cancellationToken) - { - try + public async ValueTask> GetApplicantInviteAsync(Guid userId, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var invite = await context.Invites.AsNoTracking().FirstOrDefaultAsync(i => i.Recipient == userId, cancellationToken); return invite is not null ? Result.Ok(invite.Map()) - : Result.Fail("Failed to find specific invite."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); } diff --git a/PostFundManagement.Application/Services/Evidence/EvidenceService.cs b/PostFundManagement.Application/Services/Evidence/EvidenceService.cs index e084ebc..e8523b0 100644 --- a/PostFundManagement.Application/Services/Evidence/EvidenceService.cs +++ b/PostFundManagement.Application/Services/Evidence/EvidenceService.cs @@ -1,35 +1,34 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; namespace PostFundManagement.Application.Services.Evidence; -public sealed class EvidenceService(IDbContextFactory contextFactory, HashService hashService, +public sealed class EvidenceService(IDbContextFactory contextFactory, HashService hashService, [FromKeyedServices(Constants.EvidenceBucketName)] IS3Service s3Service) : IService { - public async ValueTask 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(form["type"], out var type); + public async ValueTask UploadEvidenceFileAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + if (!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(form["type"], out var type)) + return Result.Fail(CreateError.Create("Invalid or missing form data parameters.")); - using var stream = file.OpenReadStream(); + await 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."); + return Result.Fail(uploadResult.Errors); var documentUrl = uploadResult.Value; - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidence = new Domain.Entities.Evidence { @@ -47,47 +46,32 @@ public sealed class EvidenceService(IDbContextFactory cont 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)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) - { - try - { + public async ValueTask RecordIndicatorActualAmountAsync(string indicatorIdHash, decimal amount, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { var decodeResult = hashService.DecodeLongIdHash(indicatorIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var totalUpdated = await context.Indicators.Where(i => i.Id == decodeResult.Value) + var changedTotal = 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.FailIf(changedTotal <= 0, UpdateError.New); + })); + + public async ValueTask> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask> GetIndicatorsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var indicators = await context.Indicators.AsNoTracking() .OrderByDescending(i => i.CreatedAt) @@ -95,19 +79,13 @@ public sealed class EvidenceService(IDbContextFactory cont .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)); - } - } + return Result.Ok(indicators.Select(i => i.Map()).ToArray()); + })); - public async ValueTask> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetEvidenceFilesAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidence = await context.Evidences.AsNoTracking() .OrderByDescending(e => e.CreatedAt) @@ -115,22 +93,16 @@ public sealed class EvidenceService(IDbContextFactory cont .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)); - } - } + return Result.Ok(evidence.Select(i => i.Map()).ToArray()); + })); - public async ValueTask CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateMilestoneAsync(CreateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await 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."); + return Result.Fail(NotFoundError.New); context.Milestones.Add(new Domain.Entities.Milestone { @@ -143,27 +115,16 @@ public sealed class EvidenceService(IDbContextFactory cont 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)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateIndicatorAsync(CreateIndicatorRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - 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"); + await 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."); + return Result.Fail(NotFoundError.New); context.Indicators.Add(new Domain.Entities.Indicator { @@ -177,72 +138,55 @@ public sealed class EvidenceService(IDbContextFactory cont 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)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async Task> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken) - { - try + public async ValueTask> GetIndicatorAsync(string indicatorId, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(indicatorId); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var indicator = await context.Indicators.AsNoTracking().FirstOrDefaultAsync(i => i.Id == decodeResult.Value, cancellationToken); return indicator is not null ? Result.Ok(indicator.Map()) - : Result.Fail("Failed to find specific indicator."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) - { - try + : Result.Fail(NotFoundError.New); + })); + + public async ValueTask> GetEvidenceAsync(string evidenceId, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(evidenceId); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidence = await context.Evidences.AsNoTracking().FirstOrDefaultAsync(e => e.Id == decodeResult.Value, cancellationToken); return evidence is not null ? Result.Ok(evidence.Map()) - : Result.Fail("Failed to get a specific evidence record."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); - public async ValueTask> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetMilestoneEvidencesAsync(string milestoneIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(milestoneIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidences = await context.Evidences.AsNoTracking() .Where(e => e.MilestoneId == decodeResult.Value) @@ -250,24 +194,19 @@ public sealed class EvidenceService(IDbContextFactory cont .ThenByDescending(o => o.UpdatedAt) .ToListAsync(cancellationToken); - return Result.Ok(evidences?.Select(e => e.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(evidences.Select(e => e.Map()).ToArray()); + })); - public async Task> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetProjectEvidencesAsync(string projectIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidences = await context.Evidences.AsNoTracking() .Where(e => e.ProjectId == decodeResult.Value) @@ -275,11 +214,6 @@ public sealed class EvidenceService(IDbContextFactory cont .ThenBy(o => o.UpdatedAt) .ToListAsync(cancellationToken); - return Result.Ok(evidences?.Select(e => e.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(evidences.Select(e => e.Map()).ToArray()); + })); } diff --git a/PostFundManagement.Application/Services/Execution/ExecutionService.cs b/PostFundManagement.Application/Services/Execution/ExecutionService.cs index 38c55f7..db5b0b1 100644 --- a/PostFundManagement.Application/Services/Execution/ExecutionService.cs +++ b/PostFundManagement.Application/Services/Execution/ExecutionService.cs @@ -1,6 +1,7 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -9,11 +10,10 @@ namespace PostFundManagement.Application.Services.Execution; public sealed class ExecutionService(IDbContextFactory contextFactory, HashService hashService) : IService { - public async Task> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var risks = await context.Risks.AsNoTracking() .OrderByDescending(r => r.CreatedAt) @@ -21,66 +21,30 @@ public sealed class ExecutionService(IDbContextFactory con .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(risks?.Select(i => i.Map()).ToArray() ?? []); - } - catch (Exception ex) + return Result.Ok(risks.Select(i => i.Map()).ToArray()); + })); + + public async ValueTask> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask> GetProjectRisksAsync(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 risks = await context.Risks.AsNoTracking() - .Where(r => r.ProjectId == decodeResult.Value) - .OrderByDescending(r => r.CreatedAt) - .ToListAsync(cancellationToken); - - return Result.Ok(risks?.Select(i => i.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var disbursements = await context.Disbursements.AsNoTracking() .OrderByDescending(d => d.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) - .ToListAsync(cancellationToken); + .ToArrayAsync(cancellationToken); - return Result.Ok(disbursements?.Select(i => i.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(disbursements.Select(i => i.Map()).ToArray()); + })); - public async ValueTask CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await 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."); + return Result.Fail(NotFoundError.New); context.Risks.Add(new Domain.Entities.Risk { @@ -93,24 +57,16 @@ public sealed class ExecutionService(IDbContextFactory con CreatedAt = DateTime.UtcNow }); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create project risk"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await 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."); + return Result.Fail(NotFoundError.New); context.Disbursements.Add(new Domain.Entities.Disbursement { @@ -122,40 +78,96 @@ public sealed class ExecutionService(IDbContextFactory con CreatedAt = DateTime.UtcNow }); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create disbursement"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) - { - try - { + public async ValueTask ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { var decodeResult = hashService.DecodeLongIdHash(disbursementIdHash); if (decodeResult.IsFailed) - return Result.Fail("Invalid disbursementId"); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var totalSaved = await context.Disbursements.Where(d => d.Id == decodeResult.Value) + var changedTotal = await context.Disbursements.Where(d => d.Id == decodeResult.Value) .ExecuteUpdateAsync(f => f.SetProperty(f => f.Status, ApprovalStatus.Approved) .SetProperty(f => f.UpdatedBy, userId) .SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken); - return totalSaved > 0 - ? Result.Ok() - : Result.Fail("Failed to approve payment"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); + + public async ValueTask> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => + { + var decodeResult = hashService.DecodeLongIdHash(disbursementIdHash); + + if (decodeResult.IsFailed) + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var disbursement = await context.Disbursements.AsNoTracking() + .FirstOrDefaultAsync(d => d.Id == decodeResult.Value, cancellationToken); + + return disbursement is not null + ? Result.Ok(disbursement.Map()) + : Result.Fail(NotFoundError.New); + })); + + public async ValueTask> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => + { + var decodeResult = hashService.DecodeLongIdHash(riskIdHash); + + if (decodeResult.IsFailed) + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var risk = await context.Risks.AsNoTracking() + .FirstOrDefaultAsync(r => r.Id == decodeResult.Value, cancellationToken); + + return risk is not null + ? Result.Ok(risk.Map()) + : Result.Fail(NotFoundError.New); + })); + + public async ValueTask UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var changedTotal = await context.Risks + .Where(r => r.Id == request.RiskId) + .ExecuteUpdateAsync(f => f + .SetProperty(u => u.Name, request.Name) + .SetProperty(u => u.Description, request.Description) + .SetProperty(u => u.Likelihood, request.Likelihood) + .SetProperty(u => u.Impact, request.Impact), cancellationToken); + + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); + + public async ValueTask UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var changedTotal = await context.Disbursements + .Where(f => f.Id == request.DisbursementId) + .ExecuteUpdateAsync(f => f + .SetProperty(d => d.MilestoneId, request.MilestoneId) + .SetProperty(d => d.Amount, request.Amount) + .SetProperty(d => d.UpdatedAt, DateTime.UtcNow) + .SetProperty(d => d.UpdatedBy, userId), cancellationToken); + + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); } diff --git a/PostFundManagement.Application/Services/Execution/Records.cs b/PostFundManagement.Application/Services/Execution/Records.cs index 0538b4c..a45f54a 100644 --- a/PostFundManagement.Application/Services/Execution/Records.cs +++ b/PostFundManagement.Application/Services/Execution/Records.cs @@ -2,6 +2,10 @@ namespace PostFundManagement.Application.Services.Execution; +public record UpdateDisbursementRequest(long DisbursementId, long? MilestoneId, decimal Amount); + +public record UpdateRiskRequest(long RiskId, RiskLikelihood Likelihood, RiskImpact Impact, string Name, string Description); + public record CreateDisbursementRequest(long ProjectId, long? MilestoneId, decimal Amount); public record CreateRiskRequest(long ProjectId, RiskLikelihood Likelihood, RiskImpact Impact, string Name, string Description); diff --git a/PostFundManagement.Application/Services/Governance/GovernanceService.cs b/PostFundManagement.Application/Services/Governance/GovernanceService.cs index b1c9008..f165ee3 100644 --- a/PostFundManagement.Application/Services/Governance/GovernanceService.cs +++ b/PostFundManagement.Application/Services/Governance/GovernanceService.cs @@ -1,6 +1,7 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -9,43 +10,30 @@ namespace PostFundManagement.Application.Services.Governance; public sealed class GovernanceService(IDbContextFactory contextFactory, HashService hashService) : IService { - public async ValueTask> GetProgrammesAsync(CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetProgrammesAsync(CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var programmes = await context.Programmes.AsNoTracking().ToListAsync(cancellationToken); - return Result.Ok(programmes?.Select(p => p.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(programmes.Select(p => p.Map()).ToArray()); + })); - public async ValueTask> GetPorfoliosAsync(CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetPorfoliosAsync(CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var portfolios = await context.Portfolios.AsNoTracking().ToListAsync(cancellationToken); - return Result.Ok(portfolios?.Select(p => p.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(portfolios.Select(p => p.Map()).ToArray()); + })); - public async ValueTask> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetOrganisationsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var organisations = await context.Organisations.AsNoTracking() .OrderBy(o => o.Name) @@ -53,59 +41,42 @@ public sealed class GovernanceService(IDbContextFactory co .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(organisations?.Select(o => o.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(organisations.Select(o => o.Map()).ToArray()); + })); - public async ValueTask> GetOrganisationByIdAsync(string organisationIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetOrganisationByIdAsync(string organisationIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(organisationIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var organisation = await context.Organisations.AsNoTracking() .FirstOrDefaultAsync(o => o.Id == decodeResult.Value, cancellationToken); return organisation is not null ? Result.Ok(organisation.Map()) - : Result.Fail("Failed to find specific organisation"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); - public async ValueTask> GetInstrumentsAsync(CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetInstrumentsAsync(CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var instruments = await context.Instruments.AsNoTracking().ToListAsync(cancellationToken); - return Result.Ok(instruments?.Select(i => i.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(instruments.Select(i => i.Map()).ToArray()); + })); - public async ValueTask> GetDashboardAsync(CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetDashboardAsync(CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var totalPortfolioValue = await context.Awards.SumAsync(a => a.Amount, cancellationToken); var activeAwardsCount = await context.Awards.CountAsync(a => a.Status == ApprovalStatus.Approved, cancellationToken); @@ -126,21 +97,15 @@ public sealed class GovernanceService(IDbContextFactory co return Result.Ok(new Dashboard(totalPortfolioValue, activeAwardsCount, disbursedAmount, onTrackPercentage, atRiskCount, beneficiariesReached)); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + })); - public async ValueTask CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + public async ValueTask CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Organisations.AnyAsync(o => o.RegistrationNo == request.RegistrationNo, cancellationToken)) - return Result.Fail("Organisation with the same registration number already exists"); + return Result.Fail(CreateError.Create("Organisation with the same registration number already exists")); context.Organisations.Add(new Domain.Entities.Organisation { @@ -153,13 +118,6 @@ public sealed class GovernanceService(IDbContextFactory co CreatedAt = DateTime.UtcNow }); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create organisation"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); } diff --git a/PostFundManagement.Application/Services/Grants/GrantsService.cs b/PostFundManagement.Application/Services/Grants/GrantsService.cs index 09d0167..c191589 100644 --- a/PostFundManagement.Application/Services/Grants/GrantsService.cs +++ b/PostFundManagement.Application/Services/Grants/GrantsService.cs @@ -1,6 +1,7 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -9,11 +10,10 @@ namespace PostFundManagement.Application.Services.Grants; public sealed class GrantsService(IDbContextFactory contextFactory, HashService hashService) : IService { - public async ValueTask> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var contracts = await context.Contracts.AsNoTracking() .OrderByDescending(c => c.CreatedAt) @@ -21,42 +21,32 @@ public sealed class GrantsService(IDbContextFactory contex .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(contracts?.Select(c => c.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(contracts.Select(c => c.Map()).ToArray()); + })); - public async ValueTask> GetContractByIdAsync(string contractIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(contractIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var contract = await context.Contracts.AsNoTracking().FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken); + var contract = await context.Contracts.AsNoTracking() + .FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken); return contract is not null ? Result.Ok(contract.Map()) - : Result.Fail("Failed to find specific contract"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); - public async ValueTask> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var budgets = await context.Budgets.AsNoTracking() .OrderByDescending(b => b.CreatedAt) @@ -64,42 +54,32 @@ public sealed class GrantsService(IDbContextFactory contex .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(budgets?.Select(b => b.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(budgets.Select(b => b.Map()).ToArray()); + })); - public async ValueTask> GetBudgetByIdAsync(string budgetIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(budgetIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var budget = await context.Budgets.AsNoTracking().FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken); + var budget = await context.Budgets.AsNoTracking() + .FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken); return budget is not null ? Result.Ok(budget.Map()) - : Result.Fail("Failed to get specific budget."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); - public async ValueTask> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var awards = await context.Awards.AsNoTracking() .OrderByDescending(a => a.CreatedAt) @@ -107,42 +87,31 @@ public sealed class GrantsService(IDbContextFactory contex .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(awards?.Select(a => a.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(awards.Select(a => a.Map()).ToArray()); + })); - public async ValueTask> GetAwardByIdAsync(string awardIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(awardIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var award = await context.Awards.AsNoTracking().FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken); return award is not null ? Result.Ok(award.Map()) - : Result.Fail("Award not found"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); - public async ValueTask ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Contracts .Where(c => c.Id == request.ContractId && c.Status != ContractStatus.Active) @@ -152,27 +121,19 @@ public sealed class GrantsService(IDbContextFactory contex .SetProperty(u => u.CreatedBy, userId) .SetProperty(u => u.Status, ContractStatus.Active), cancellationToken); - return changedTotal > 0 - ? Result.Ok() - : Result.Fail("Contract activation failed"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, UpdateError.New); + })); - public async ValueTask CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var award = await context.Awards.AsNoTracking() .FirstOrDefaultAsync(a => a.Id == request.AwardId, cancellationToken); if (award == null) - return Result.Fail($"Award with ID {request.AwardId} does not exist."); + return Result.Fail(NotFoundError.New); var contract = new Domain.Entities.Contract { @@ -187,27 +148,19 @@ public sealed class GrantsService(IDbContextFactory contex context.Contracts.Add(contract); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create contract"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { if (request.Amount <= 0) - return Result.Fail("Budget amount must be greater than zero."); + return Result.Fail(ValidationError.Create("Budget amount must be greater than zero.")); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await 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."); + return Result.Fail(NotFoundError.New); var budget = new Domain.Entities.Budget { @@ -220,30 +173,22 @@ public sealed class GrantsService(IDbContextFactory contex context.Budgets.Add(budget); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create budget"); - } - catch (Exception ex) + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); + + public async ValueTask CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try - { if (request.Amount <= 0) - return Result.Fail("Approved amount must be greater than zero."); + return Result.Fail(ValidationError.Create("Approved amount must be greater than zero.")); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Organisations.AnyAsync(o => o.Id == request.OrganisationId, cancellationToken)) - return Result.Fail($"Organisation with ID {request.OrganisationId} does not exist."); + return Result.Fail(NotFoundError.New); if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken)) - return Result.Fail($"Programme with ID {request.ProgrammeId} does not exist."); + return Result.Fail(NotFoundError.New); var award = new Domain.Entities.Award { @@ -257,13 +202,6 @@ public sealed class GrantsService(IDbContextFactory contex context.Awards.Add(award); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create award."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); } diff --git a/PostFundManagement.Application/Services/Projects/ProjectService.cs b/PostFundManagement.Application/Services/Projects/ProjectService.cs index 07c138d..be0e319 100644 --- a/PostFundManagement.Application/Services/Projects/ProjectService.cs +++ b/PostFundManagement.Application/Services/Projects/ProjectService.cs @@ -1,6 +1,7 @@ -using PostFundManagement.Application.Shared; +using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -9,16 +10,15 @@ namespace PostFundManagement.Application.Services.Projects; public sealed class ProjectService(IDbContextFactory contextFactory, HashService hashService) : IService { - public async ValueTask UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken); if (project == null) - return Result.Fail("Project does not exist."); + return Result.Fail(NotFoundError.New); project.Status = request.Status; project.UpdatedBy = userId; @@ -33,93 +33,69 @@ public sealed class ProjectService(IDbContextFactory conte .SetProperty(p => p.UpdatedBy, userId) .SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken); - return changedTotal > 0 - ? Result.Ok() - : Result.Fail("Failed to update project"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(changedTotal == 0, UpdateError.New); + })); - public async ValueTask> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var projects = await context.Projects.AsNoTracking().OrderByDescending(p => p.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) .ToListAsync(cancellationToken); - return Result.Ok(projects?.Select(p => p.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(projects.Select(p => p.Map()).ToArray()); + })); - public async ValueTask> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var project = await context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken); + var project = await context.Projects.AsNoTracking() + .FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken); return project is not null ? Result.Ok(project.Map()) - : Result.Fail("Failed to get specific project."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.Create()); + })); - public async ValueTask> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create().CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var activities = await context.Activities.AsNoTracking() .Where(a => a.ProjectId == decodeResult.Value) .OrderByDescending(a => a.CreatedAt) .ToListAsync(cancellationToken); - return Result.Ok(activities?.Select(a => a.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(activities.Select(a => a.Map()).ToArray()); + })); - public async ValueTask CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try + public async ValueTask CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Projects.AnyAsync(p => p.Name == request.Name && p.Sector == request.Sector && p.ProjectType == request.ProjectType, cancellationToken)) - return Result.Fail("A similarly named project exists of the same project type and sector"); + return Result.Fail(ValidationError.Create("A similarly named project exists of the same project type and sector")); if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken)) - return Result.Fail($"Programme does not exist."); + return Result.Fail(NotFoundError.New); context.Projects.Add(new Domain.Entities.Project { @@ -133,28 +109,20 @@ public sealed class ProjectService(IDbContextFactory conte CreatedAt = DateTime.UtcNow }); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create project"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + public async ValueTask CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken)) - return Result.Fail($"Project does not exist."); + return Result.Fail(NotFoundError.New); if (request.MilestoneId.HasValue) if (!await context.Milestones.AnyAsync(m => m.Id == request.MilestoneId.Value, cancellationToken)) - return Result.Fail($"Milestone does not exist."); + return Result.Fail(NotFoundError.New); context.Activities.Add(new Domain.Entities.Activity { @@ -170,93 +138,69 @@ public sealed class ProjectService(IDbContextFactory conte CreatedAt = DateTime.UtcNow }); - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to create activity."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetProjectMilestonesAsync(string projectIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await 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)); - } - } + return Result.Ok(milestones.Select(i => i.Map()).ToArray()); + })); - public async Task> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken) - { - try + public async ValueTask> GetProjectIndicatorsAsync(string projectIdHash, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var evidences = await context.Indicators.AsNoTracking() .Where(e => e.ProjectId == decodeResult.Value) .OrderByDescending(o => o.Id) .ToListAsync(cancellationToken); - return Result.Ok(evidences?.Select(e => e.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(evidences.Select(e => e.Map()).ToArray()); + })); - public async Task> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken) - { - try + public async ValueTask> GetMilestoneAsync(string milestoneIdHash, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(milestoneIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create().CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var milestone = await context.Milestones.AsNoTracking().FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken); + var milestone = await context.Milestones.AsNoTracking() + .FirstOrDefaultAsync(m => m.Id == decodeResult.Value, cancellationToken); return milestone is not null ? Result.Ok(milestone.Map()) - : Result.Fail("Failed to get specific milestone"); + : Result.Fail(NotFoundError.Create()); + })); - } - catch (Exception ex) + public async ValueTask> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } - - public async ValueTask> GetMilestonesAsync(Pagination pagination, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var milestones = await context.Milestones.AsNoTracking() .OrderByDescending(m => m.CreatedAt) @@ -264,19 +208,13 @@ public sealed class ProjectService(IDbContextFactory conte .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)); - } - } + return Result.Ok(milestones.Select(i => i.Map()).ToArray()); + })); - public async Task UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + public async ValueTask UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Milestones.Where(m => m.Id == request.MilestoneId) .ExecuteUpdateAsync(f => f @@ -287,21 +225,13 @@ public sealed class ProjectService(IDbContextFactory conte .SetProperty(u => u.UpdatedBy, userId) .SetProperty(u => u.UpdatedAt, DateTime.UtcNow), cancellationToken); - return changedTotal > 0 - ? Result.Ok() - : Result.Fail("Failed to update milestone"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); - public async Task UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken) - { - try + public async ValueTask UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Projects.Where(p => p.Id == request.ProjectId) .ExecuteUpdateAsync(f => f @@ -314,36 +244,44 @@ public sealed class ProjectService(IDbContextFactory conte .SetProperty(u => u.Sector, request.Sector) .SetProperty(u => u.ProjectType, request.ProjectType), cancellationToken); - return changedTotal > 0 - ? Result.Ok() - : Result.Fail("Failed to update project"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); - public async ValueTask> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken) - { - try + public async ValueTask> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(activityIdHash); if (decodeResult.IsFailed) - return Result.Fail(decodeResult.Errors); + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var activity = await context.Activities.AsNoTracking().FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken); + var activity = await context.Activities.AsNoTracking() + .FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken); return activity is not null ? Result.Ok(activity.Map()) - : Result.Fail("Failed to get specific activity."); - } - catch (Exception ex) + : Result.Fail(NotFoundError.New); + })); + + public async ValueTask> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + var decodeResult = hashService.DecodeLongIdHash(projectIdHash); + + if (decodeResult.IsFailed) + return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var risks = await context.Risks.AsNoTracking() + .Where(r => r.ProjectId == decodeResult.Value) + .OrderByDescending(r => r.CreatedAt) + .ToListAsync(cancellationToken); + + return Result.Ok(risks.Select(i => i.Map()).ToArray()); + })); } diff --git a/PostFundManagement.Application/Services/Shared/BrowserLocalStorageService.cs b/PostFundManagement.Application/Services/Shared/BrowserLocalStorageService.cs index 9aa7715..8bba042 100644 --- a/PostFundManagement.Application/Services/Shared/BrowserLocalStorageService.cs +++ b/PostFundManagement.Application/Services/Shared/BrowserLocalStorageService.cs @@ -1,78 +1,50 @@ +using PostFundManagement.Domain.Errors; + namespace PostFundManagement.Application.Services.Shared; public sealed class BrowserLocalStorageService(ProtectedLocalStorage storage) { - public async ValueTask DeleteAsync(string key) - { - try + public async ValueTask DeleteAsync(string key) => + await Result.Try((Func>)(async () => { await storage.DeleteAsync(key); return Result.Ok(); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + })); - public async ValueTask SaveAsync(string key, string value) - { - try + public async ValueTask SaveAsync(string key, string value) => + await Result.Try((Func>)(async () => { await storage.SetAsync(key, value); return Result.Ok(); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + })); - public async ValueTask SaveAsync(string key, TValue value) where TValue : class - { - try + public async ValueTask SaveAsync(string key, TValue value) where TValue : class => + await Result.Try((Func>)(async () => { await storage.SetAsync(key, value); return Result.Ok(); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + })); - public async ValueTask> GetAsync(string key) - { - try + public async ValueTask> GetAsync(string key) => + await Result.Try((Func>>)(async () => { var retrieval = await storage.GetAsync(key); return retrieval.Success && !string.IsNullOrWhiteSpace(retrieval.Value) ? Result.Ok(retrieval.Value) - : Result.Fail($"Could not find object by key {key}"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.Create($"Could not find object by key {key}")); + })); - public async ValueTask> GetAsync(string key) where TValue : class - { - try + public async ValueTask> GetAsync(string key) where TValue : class => + await Result.Try((Func>>)(async () => { var retrieval = await storage.GetAsync(key); return retrieval.Success && retrieval.Value is not null ? Result.Ok(retrieval.Value) - : Result.Fail($"Could not find object by key {key}"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.Create($"Could not find object by key {key}")); + })); } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Shared/ContractS3Service.cs b/PostFundManagement.Application/Services/Shared/ContractS3Service.cs index 3eac12c..33b5965 100644 --- a/PostFundManagement.Application/Services/Shared/ContractS3Service.cs +++ b/PostFundManagement.Application/Services/Shared/ContractS3Service.cs @@ -7,6 +7,6 @@ namespace PostFundManagement.Application.Services.Shared; public sealed class ContractS3Service(IConfiguration configuration, [FromKeyedServices(ContractBucketName)] IAmazonS3 amazonS3) : S3ServiceBase(amazonS3), IS3Service { - protected override string BucketName => configuration.GetSection($"{ContractS3SettingsSection}:BucketName").Value ?? ""; - protected override string CdnBaseUrl => configuration.GetSection($"{ContractS3SettingsSection}:CdnBaseUrl").Value ?? ""; + protected override string BucketName => configuration.GetSection($"{ContractS3SettingsSection}:BucketName").Value ?? string.Empty; + protected override string CdnBaseUrl => configuration.GetSection($"{ContractS3SettingsSection}:CdnBaseUrl").Value ?? string.Empty; } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Shared/EvidenceS3Service.cs b/PostFundManagement.Application/Services/Shared/EvidenceS3Service.cs index aac46f7..2116867 100644 --- a/PostFundManagement.Application/Services/Shared/EvidenceS3Service.cs +++ b/PostFundManagement.Application/Services/Shared/EvidenceS3Service.cs @@ -7,6 +7,6 @@ namespace PostFundManagement.Application.Services.Shared; public sealed class EvidenceS3Service(IConfiguration configuration, [FromKeyedServices(EvidenceBucketName)] IAmazonS3 amazonS3) : S3ServiceBase(amazonS3), IS3Service { - protected override string BucketName => configuration.GetSection($"{EvidenceS3SettingsSection}:BucketName").Value ?? ""; - protected override string CdnBaseUrl => configuration.GetSection($"{EvidenceS3SettingsSection}:CdnBaseUrl").Value ?? ""; + protected override string BucketName => configuration.GetSection($"{EvidenceS3SettingsSection}:BucketName").Value ?? string.Empty; + protected override string CdnBaseUrl => configuration.GetSection($"{EvidenceS3SettingsSection}:CdnBaseUrl").Value ?? string.Empty; } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Shared/GeneralS3Service.cs b/PostFundManagement.Application/Services/Shared/GeneralS3Service.cs index aa5bd7b..db9a23f 100644 --- a/PostFundManagement.Application/Services/Shared/GeneralS3Service.cs +++ b/PostFundManagement.Application/Services/Shared/GeneralS3Service.cs @@ -7,6 +7,6 @@ namespace PostFundManagement.Application.Services.Shared; public sealed class GeneralS3Service(IConfiguration configuration, [FromKeyedServices(GeneralBucketName)] IAmazonS3 amazonS3) : S3ServiceBase(amazonS3), IS3Service { - protected override string BucketName => configuration.GetSection($"{GeneralS3SettingsSection}:BucketName").Value ?? ""; - protected override string CdnBaseUrl => configuration.GetSection($"{GeneralS3SettingsSection}:CdnBaseUrl").Value ?? ""; + protected override string BucketName => configuration.GetSection($"{GeneralS3SettingsSection}:BucketName").Value ?? string.Empty; + protected override string CdnBaseUrl => configuration.GetSection($"{GeneralS3SettingsSection}:CdnBaseUrl").Value ?? string.Empty; } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Shared/HashService.cs b/PostFundManagement.Application/Services/Shared/HashService.cs index 8e5bafe..6d0e3df 100644 --- a/PostFundManagement.Application/Services/Shared/HashService.cs +++ b/PostFundManagement.Application/Services/Shared/HashService.cs @@ -1,6 +1,7 @@ using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; -namespace PostFundManagement.Application.Shared; +namespace PostFundManagement.Application.Services.Shared; public sealed partial class HashService(IHashids hasher) : IService { @@ -31,59 +32,55 @@ public sealed partial class HashService(IHashids hasher) : IService public static Result ToMd5Hash(string input) { if (string.IsNullOrEmpty(input)) - return Result.Fail("Input content cannot be null or empty for MD5 processing."); + return Result.Fail(ValidationError.Create("Input content cannot be null or empty for MD5 processing.")); byte[] bytes = MD5.HashData(Encoding.UTF8.GetBytes(input)); + return Result.Ok(Convert.ToHexString(bytes).ToLowerInvariant()); } public Result HashEncodeHex(string input) => string.IsNullOrWhiteSpace(input) || !HexHashRegex.IsMatch(input) - ? Result.Fail("Input must be a valid hexadecimal string.") + ? Result.Fail(ValidationError.Create("Input must be a valid hexadecimal string.")) : Result.Ok(hasher.EncodeHex(input)); public Result HashEncodeIntId(int id) => id < 0 - ? Result.Fail("Id cannot be negative.") + ? Result.Fail(ValidationError.Create("Id cannot be negative.")) : Result.Ok(hasher.Encode(id)); public Result HashEncodeLongId(long id) => id < 0 - ? Result.Fail("Id cannot be negative.") + ? Result.Fail(ValidationError.Create("Id cannot be negative.")) : Result.Ok(hasher.EncodeLong(id)); public Result DecodeIntIdHash(string hash) { - if (string.IsNullOrWhiteSpace(hash)) return Result.Fail("Invalid token layout."); + if (string.IsNullOrWhiteSpace(hash)) + return Result.Fail(ValidationError.Create("Invalid token layout.")); int[] decoded = hasher.Decode(hash); - return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail("Invalid or modified Int hash token."); + return decoded.Length == 1 + ? Result.Ok(decoded[0]) : + Result.Fail(ValidationError.Create("Invalid or modified Int hash token.")); } public Result DecodeLongIdHash(string hash) { - if (string.IsNullOrWhiteSpace(hash)) return Result.Fail("Invalid token layout."); + if (string.IsNullOrWhiteSpace(hash)) + return Result.Fail(ValidationError.Create("Invalid token layout.")); long[] decoded = hasher.DecodeLong(hash); - return decoded.Length == 1 ? Result.Ok(decoded[0]) : Result.Fail("Invalid or modified Long hash token."); + return decoded.Length == 1 ? Result.Ok(decoded[0]) : + Result.Fail(ValidationError.Create("Invalid or modified Long hash token.")); } - public Result DecodeHexHash(string hex) - { - try + public async Task> DecodeHexHash(string hex) => + await Result.Try((Func>>)(async () => { string decoded = hasher.DecodeHex(hex); return string.IsNullOrEmpty(decoded) - ? Result.Fail("Invalid or corrupted hex hash.") + ? Result.Fail(ValidationError.Create("Invalid or corrupted hex hash.")) : Result.Ok(decoded); - } - catch (FormatException fex) - { - return Result.Fail(new Error("Invalid hash structure.").CausedBy(fex)); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + })); } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Shared/IdentityService.cs b/PostFundManagement.Application/Services/Shared/IdentityService.cs index 356cb5d..5293eea 100644 --- a/PostFundManagement.Application/Services/Shared/IdentityService.cs +++ b/PostFundManagement.Application/Services/Shared/IdentityService.cs @@ -1,24 +1,20 @@ using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Infrastructure.Database; namespace PostFundManagement.Application.Services.Shared; public sealed class IdentityService(IDbContextFactory contextFactory) : IService { - public async ValueTask UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) - { - try - { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + public async ValueTask UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - return !await context.Users.AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken) - ? Result.Fail("Invalid user") - : Result.Ok(); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + var userExistsAndActive = await context.Users + .AnyAsync(u => u.Id == userId && u.Status == ActivityStatus.Active, cancellationToken); + + return Result.FailIf(!userExistsAndActive, NotFoundError.Create()); + })); } diff --git a/PostFundManagement.Application/Services/Shared/TokenService.cs b/PostFundManagement.Application/Services/Shared/TokenService.cs index f046b34..3eb386d 100644 --- a/PostFundManagement.Application/Services/Shared/TokenService.cs +++ b/PostFundManagement.Application/Services/Shared/TokenService.cs @@ -1,3 +1,4 @@ +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Security.Configuration; using PostFundManagement.Domain.Security.Models; using PostFundManagement.Domain.Security.Sdk; @@ -8,24 +9,21 @@ public sealed class TokenService(ISecurityConnectApi connectApi, IOptions> GenerateAsync(CancellationToken cancellationToken = default) - { - try + public async ValueTask> GenerateAsync(CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - var request = new Domain.Security.Models.TokenRequest + using var response = await connectApi.GetToken(new Domain.Security.Models.TokenRequest { ClientId = clientSettings.ClientId, ClientSecret = clientSettings.ClientSecret, GrantType = clientSettings.GrantType, Scope = clientSettings.Scope, - }; - - using var response = await connectApi.GetToken(request, cancellationToken); + }, cancellationToken); var contentRaw = await response.Content.ReadAsStringAsync(cancellationToken); if (string.IsNullOrWhiteSpace(contentRaw)) - return Result.Fail(new Error($"The authentication endpoint returned an empty payload. Status code: {response.StatusCode}")); + return Result.Fail(ValidationError.Create($"The authentication endpoint returned an empty payload. Status code: {response.StatusCode}")); if (response.IsSuccessStatusCode) { @@ -33,34 +31,15 @@ public sealed class TokenService(ISecurityConnectApi connectApi, IOptions(new Error("Authentication succeeded, but no access token was found in the response payload.")); + : Result.Fail(NotFoundError.Create("Authentication succeeded, but no access token was found in the response payload.")); } - try + return await Result.Try((Func>>)(async () => { var errorResult = JsonSerializer.Deserialize(contentRaw); - if (errorResult != null) - { - string summary = $"{errorResult.Error}: {errorResult.ErrorDescription}"; - - return Result.Fail(new Error(summary)); - } - } - catch - { - return Result.Fail(new Error($"Authentication failed: {contentRaw}")); - } - - return Result.Fail(new Error($"Authentication failed with status code: {response.StatusCode}")); - } - catch (OperationCanceledException ex) - { - return Result.Fail(new Error("The token generation request was canceled.").CausedBy(ex)); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(errorResult != null, + CreateError.Create($"{errorResult!.Error}: {errorResult.ErrorDescription}")); + })); + })); } \ No newline at end of file diff --git a/PostFundManagement.Application/Services/Users/UserService.cs b/PostFundManagement.Application/Services/Users/UserService.cs index 1292eb9..6be2ad9 100644 --- a/PostFundManagement.Application/Services/Users/UserService.cs +++ b/PostFundManagement.Application/Services/Users/UserService.cs @@ -1,5 +1,6 @@ using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; @@ -8,11 +9,10 @@ namespace PostFundManagement.Application.Services.Users; public sealed class UserService(IDbContextFactory contextFactory) : IService { - public async ValueTask SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) - { - try + public async ValueTask SyncUserAsync(Guid userId, string email, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var user = await context.Users.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); @@ -32,21 +32,13 @@ public sealed class UserService(IDbContextFactory contextF context.Users.Update(user); } - return await context.SaveChangesAsync(cancellationToken) > 0 - ? Result.Ok() - : Result.Fail("Failed to sync user"); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); - public async ValueTask> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) - { - try + public async ValueTask> GetUsersAsync(Pagination pagination, UserFilterRequest request, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var query = context.Users.AsNoTracking(); @@ -67,54 +59,34 @@ public sealed class UserService(IDbContextFactory contextF else query = query.OrderBy(u => u.Id); - var totalCount = await query.CountAsync(cancellationToken); - var users = await query.Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize).ToListAsync(cancellationToken); - return Result.Ok(users?.Select(u => u.Map()).ToArray() ?? []); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.Ok(users.Select(u => u.Map()).ToArray()); + })); - public async ValueTask UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default) - { - try + public async ValueTask UpdateUserStatusAsync(Guid userId, ActivityStatus status, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Users .Where(f => f.Id == userId) .ExecuteUpdateAsync(f => f.SetProperty(u => u.Status, status), cancellationToken); - return changedTotal > 0 - ? Result.Ok() - : Result.Fail("Failed to update user activity status."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); - public async Task> GetUserAsync(Guid userId, CancellationToken cancellationToken) - { - try + public async ValueTask> GetUserAsync(Guid userId, CancellationToken cancellationToken) => + await Result.Try((Func>>)(async () => { - using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); - var user = await context.Users.AsNoTracking().FirstOrDefaultAsync(f => f.Id == userId, cancellationToken); + var user = await context.Users.AsNoTracking() + .FirstOrDefaultAsync(f => f.Id == userId, cancellationToken); return user is not null ? Result.Ok(user.Map()) - : Result.Fail("Failed to find specific user."); - } - catch (Exception ex) - { - return Result.Fail(new Error(ex.Message).CausedBy(ex)); - } - } + : Result.Fail(NotFoundError.New); + })); } diff --git a/PostFundManagement.Domain/Abstractions/IS3Service.cs b/PostFundManagement.Domain/Abstractions/IS3Service.cs index c47083b..a1d6a3e 100644 --- a/PostFundManagement.Domain/Abstractions/IS3Service.cs +++ b/PostFundManagement.Domain/Abstractions/IS3Service.cs @@ -2,6 +2,6 @@ namespace PostFundManagement.Domain.Abstractions; public interface IS3Service { - Task> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default); - Task DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default); + ValueTask> UploadFileAsync(string fileName, Stream fileStream, string contentType, CancellationToken cancellationToken = default); + ValueTask DeleteFileAsync(string fileKey, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/PostFundManagement.Domain/Errors/CreateError.cs b/PostFundManagement.Domain/Errors/CreateError.cs new file mode 100644 index 0000000..70db482 --- /dev/null +++ b/PostFundManagement.Domain/Errors/CreateError.cs @@ -0,0 +1,14 @@ +namespace PostFundManagement.Domain.Errors; + +public sealed class CreateError : Error +{ + public static CreateError New { get; } = new(); + + public CreateError() : base(string.Empty) { } + + public CreateError(string message) : base(message) { } + + public static CreateError Create() => new(); + + public static CreateError Create(string message) => new(message); +} diff --git a/PostFundManagement.Domain/Errors/IdDecodeError.cs b/PostFundManagement.Domain/Errors/IdDecodeError.cs new file mode 100644 index 0000000..4222acd --- /dev/null +++ b/PostFundManagement.Domain/Errors/IdDecodeError.cs @@ -0,0 +1,14 @@ +namespace PostFundManagement.Domain.Errors; + +public sealed class IdDecodeError : Error +{ + public static IdDecodeError New { get; } = new(); + + public IdDecodeError() : base(string.Empty) { } + + public IdDecodeError(string message) : base(message) { } + + public static IdDecodeError Create() => new(); + + public static IdDecodeError Create(string message) => new(message); +} diff --git a/PostFundManagement.Domain/Errors/NotFoundError.cs b/PostFundManagement.Domain/Errors/NotFoundError.cs new file mode 100644 index 0000000..7d341cb --- /dev/null +++ b/PostFundManagement.Domain/Errors/NotFoundError.cs @@ -0,0 +1,14 @@ +namespace PostFundManagement.Domain.Errors; + +public sealed class NotFoundError : Error +{ + public static NotFoundError New { get; } = new(); + + public NotFoundError() : base(string.Empty) { } + + public NotFoundError(string Message) : base(Message) { } + + public static NotFoundError Create() => new(); + + public static NotFoundError Create(string message) => new(message); +} diff --git a/PostFundManagement.Domain/Errors/UpdateError.cs b/PostFundManagement.Domain/Errors/UpdateError.cs new file mode 100644 index 0000000..d0e7b9c --- /dev/null +++ b/PostFundManagement.Domain/Errors/UpdateError.cs @@ -0,0 +1,14 @@ +namespace PostFundManagement.Domain.Errors; + +public sealed class UpdateError : Error +{ + public static UpdateError New { get; } = new(); + + public UpdateError() : base(string.Empty) { } + + public UpdateError(string message) : base(message) { } + + public static UpdateError Create() => new(); + + public static UpdateError Create(string message) => new(message); +} diff --git a/PostFundManagement.Domain/Errors/ValidationError.cs b/PostFundManagement.Domain/Errors/ValidationError.cs new file mode 100644 index 0000000..1d2b61c --- /dev/null +++ b/PostFundManagement.Domain/Errors/ValidationError.cs @@ -0,0 +1,14 @@ +namespace PostFundManagement.Domain.Errors; + +public sealed class ValidationError : Error +{ + public static ValidationError New { get; } = new(); + + public ValidationError() : base(string.Empty) { } + + public ValidationError(string message) : base(message) { } + + public static ValidationError Create() => new(); + + public static ValidationError Create(string message) => new(message); +} diff --git a/PostFundManagement.Domain/Extensions/Constants.cs b/PostFundManagement.Domain/Extensions/Constants.cs index 79117aa..00b8729 100644 --- a/PostFundManagement.Domain/Extensions/Constants.cs +++ b/PostFundManagement.Domain/Extensions/Constants.cs @@ -2,7 +2,7 @@ namespace PostFundManagement.Domain.Extensions; public static class Constants { - public const string EmaiItemKey = "UserEmail"; + public const string EmailtemKey = "UserEmail"; public const string UserIdItemKey = "UserId"; From b8aa5651fa6b4a12e349dc976c38ad9a4fd6bf51 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 1 Sep 2026 12:29:00 +0200 Subject: [PATCH 2/2] Added missing endpoints --- ...sEndpoint.cs => CreateEvidenceEndpoint.cs} | 8 ++--- .../Evidence/UpdateEvidenceEndpoint.cs | 36 +++++++++++++++++++ .../Governance/CreateOrganisationEndpoint.cs | 2 +- ...Endpoint.cs => GetOrganisationEndpoint.cs} | 6 ++-- .../Governance/UpdateOrganisationEndpoint.cs | 34 ++++++++++++++++++ .../Services/Evidence/EvidenceService.cs | 7 +++- .../Services/Evidence/Records.cs | 2 ++ .../Services/Governance/GovernanceService.cs | 18 +++++++++- .../Services/Governance/Records.cs | 2 ++ 9 files changed, 105 insertions(+), 10 deletions(-) rename PostFundManagement.Api/Endpoints/Evidence/{UploadEvidenceFilesEndpoint.cs => CreateEvidenceEndpoint.cs} (84%) create mode 100644 PostFundManagement.Api/Endpoints/Evidence/UpdateEvidenceEndpoint.cs rename PostFundManagement.Api/Endpoints/Governance/{GetOrganisationByIdEndpoint.cs => GetOrganisationEndpoint.cs} (87%) create mode 100644 PostFundManagement.Api/Endpoints/Governance/UpdateOrganisationEndpoint.cs diff --git a/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/CreateEvidenceEndpoint.cs similarity index 84% rename from PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs rename to PostFundManagement.Api/Endpoints/Evidence/CreateEvidenceEndpoint.cs index a5dd1e8..c694dc8 100644 --- a/PostFundManagement.Api/Endpoints/Evidence/UploadEvidenceFilesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Evidence/CreateEvidenceEndpoint.cs @@ -9,11 +9,11 @@ using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Evidence; [ApiVersionTarget(1)] -public class UploadEvidenceFilesEndpoint : IEndpoint +public class CreateEvidenceEndpoint : IEndpoint { public void Map(IEndpointRouteBuilder builder) { - builder.MapPost("api/evidence/files", async (HttpRequest request, EvidenceService service, + builder.MapPut("api/evidence/file", async (HttpRequest request, EvidenceService service, CancellationToken cancellationToken = default) => { var userId = request.HttpContext.GetUserId(); @@ -27,7 +27,7 @@ public class UploadEvidenceFilesEndpoint : IEndpoint if (file == null || file.Length == 0) return Results.BadRequest("No file uploaded or file is empty."); - return await service.UploadEvidenceFileAsync(userId, form, file, cancellationToken) switch + return await service.CreateEvidenceAsync(userId, form, file, cancellationToken) switch { { IsSuccess: true } => Results.Ok(), var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), @@ -37,7 +37,7 @@ public class UploadEvidenceFilesEndpoint : IEndpoint .RequireAuthorization() .DisableAntiforgery() .WithDescription("Upload supporting evidence file to S3 and register metadata (M14)") - .WithName(typeof(UploadEvidenceFilesEndpoint).ToEndpointName()) + .WithName(typeof(CreateEvidenceEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) diff --git a/PostFundManagement.Api/Endpoints/Evidence/UpdateEvidenceEndpoint.cs b/PostFundManagement.Api/Endpoints/Evidence/UpdateEvidenceEndpoint.cs new file mode 100644 index 0000000..efee423 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Evidence/UpdateEvidenceEndpoint.cs @@ -0,0 +1,36 @@ +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, EvidenceService service, + CancellationToken cancellationToken = default) => + await service.UpdateEvidenceAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.Forbid(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Evidence); + } +} diff --git a/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs index 5b50c9d..16c27f0 100644 --- a/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/CreateOrganisationEndpoint.cs @@ -9,7 +9,7 @@ using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Governance; [ApiVersionTarget(1)] -public class CreateOrganisationEndpoint : IEndpoint +public sealed class CreateOrganisationEndpoint : IEndpoint { public void Map(IEndpointRouteBuilder builder) { diff --git a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationEndpoint.cs similarity index 87% rename from PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs rename to PostFundManagement.Api/Endpoints/Governance/GetOrganisationEndpoint.cs index 9fba496..b8c475a 100644 --- a/PostFundManagement.Api/Endpoints/Governance/GetOrganisationByIdEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Governance/GetOrganisationEndpoint.cs @@ -10,13 +10,13 @@ using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; namespace PostFundManagement.Api.Endpoints.Governance; [ApiVersionTarget(1)] -public class GetOrganisationByIdEndpoint : IEndpoint +public class GetOrganisationEndpoint : IEndpoint { public void Map(IEndpointRouteBuilder builder) { builder.MapGet("api/governance/organisations/{organisationId}", async (string organisationId, GovernanceService service, CancellationToken cancellationToken = default) => - await service.GetOrganisationByIdAsync(organisationId, cancellationToken) switch + await service.GetOrganisationAsync(organisationId, cancellationToken) switch { { IsSuccess: true, Value: var organisation } => Results.Ok(organisationId), var r when r.HasError() => Results.Forbid(), @@ -24,7 +24,7 @@ public class GetOrganisationByIdEndpoint : IEndpoint var r => Results.InternalServerError(r.Errors.ToProblems()) }) .WithDescription("Get organization profile by ID") - .WithName(typeof(GetOrganisationByIdEndpoint).ToEndpointName()) + .WithName(typeof(GetOrganisationEndpoint).ToEndpointName()) .MapToApiVersion(new ApiVersion(1)) .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) diff --git a/PostFundManagement.Api/Endpoints/Governance/UpdateOrganisationEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/UpdateOrganisationEndpoint.cs new file mode 100644 index 0000000..ab60568 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Governance/UpdateOrganisationEndpoint.cs @@ -0,0 +1,34 @@ +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, + GovernanceService service, CancellationToken cancellationToken = default) => + await service.UpdateOrganisationAsync(request, cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => 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(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .Produces(StatusCodes.Status401Unauthorized) + .WithTags(EndpointTags.Governance); + } +} diff --git a/PostFundManagement.Application/Services/Evidence/EvidenceService.cs b/PostFundManagement.Application/Services/Evidence/EvidenceService.cs index e8523b0..3d7bb18 100644 --- a/PostFundManagement.Application/Services/Evidence/EvidenceService.cs +++ b/PostFundManagement.Application/Services/Evidence/EvidenceService.cs @@ -11,7 +11,7 @@ namespace PostFundManagement.Application.Services.Evidence; public sealed class EvidenceService(IDbContextFactory contextFactory, HashService hashService, [FromKeyedServices(Constants.EvidenceBucketName)] IS3Service s3Service) : IService { - public async ValueTask UploadEvidenceFileAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default) => + public async ValueTask CreateEvidenceAsync(Guid userId, IFormCollection form, IFormFile file, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { if (!long.TryParse(form["projectId"], out var projectId) || !long.TryParse(form["milestoneId"], out var milestoneId) || @@ -216,4 +216,9 @@ public sealed class EvidenceService(IDbContextFactory cont return Result.Ok(evidences.Select(e => e.Map()).ToArray()); })); + + public async Task UpdateEvidenceAsync(UpdateEvidenceRequest request, Guid userId, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } diff --git a/PostFundManagement.Application/Services/Evidence/Records.cs b/PostFundManagement.Application/Services/Evidence/Records.cs index 6cd3ded..24531f7 100644 --- a/PostFundManagement.Application/Services/Evidence/Records.cs +++ b/PostFundManagement.Application/Services/Evidence/Records.cs @@ -2,6 +2,8 @@ namespace PostFundManagement.Application.Services.Evidence; +public record UpdateEvidenceRequest(long EvidenceId, int Version, EvidenceType EvidenceType, ApprovalStatus Status); + public record RecordActualRequest(decimal ActualAmount); public record CreateMilestoneRequest(long ProjectId, DateTime DueAt, string Name, Priority Priority); diff --git a/PostFundManagement.Application/Services/Governance/GovernanceService.cs b/PostFundManagement.Application/Services/Governance/GovernanceService.cs index f165ee3..f1e03fe 100644 --- a/PostFundManagement.Application/Services/Governance/GovernanceService.cs +++ b/PostFundManagement.Application/Services/Governance/GovernanceService.cs @@ -44,7 +44,7 @@ public sealed class GovernanceService(IDbContextFactory co return Result.Ok(organisations.Select(o => o.Map()).ToArray()); })); - public async ValueTask> GetOrganisationByIdAsync(string organisationIdHash, CancellationToken cancellationToken = default) => + public async ValueTask> GetOrganisationAsync(string organisationIdHash, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(organisationIdHash); @@ -120,4 +120,20 @@ public sealed class GovernanceService(IDbContextFactory co return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); })); + + public async ValueTask> UpdateOrganisationAsync(UpdateOrganisationRequest request, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var changedTotal = await context.Organisations + .Where(f => f.Id == request.OrganisationId) + .ExecuteUpdateAsync(f => f + .SetProperty(u => u.Name, request.Name) + .SetProperty(u => u.RegistrationNo, request.RegistrationNo) + .SetProperty(u => u.Email, request.Email) + .SetProperty(u => u.Type, request.Type), cancellationToken); + + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); } diff --git a/PostFundManagement.Application/Services/Governance/Records.cs b/PostFundManagement.Application/Services/Governance/Records.cs index c64aabe..0f12a5a 100644 --- a/PostFundManagement.Application/Services/Governance/Records.cs +++ b/PostFundManagement.Application/Services/Governance/Records.cs @@ -2,6 +2,8 @@ namespace PostFundManagement.Application.Services.Governance; +public record UpdateOrganisationRequest(long OrganisationId, string Name, string RegistrationNo, string Email, OrganisationType Type); + public record CreateOrganisationRequest(string Name, string RegistrationNo, string Email, OrganisationType Type); public record Dashboard(decimal PortfolioValue, int ActiveAwards, decimal Disbursed, double OnTrackPercentage, int AtRiskCount, int BeneficiariesReached);