From b8aa5651fa6b4a12e349dc976c38ad9a4fd6bf51 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 1 Sep 2026 12:29:00 +0200 Subject: [PATCH] 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);