From a218b19875ccf5a74a6330fc612cd15552f7e356 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sat, 5 Sep 2026 10:27:52 +0200 Subject: [PATCH] Added program control endpoints --- .../Projects/CreateProgrammeEndpoint.cs | 37 ++++++++ .../Projects/GetMilestonesEndpoint.cs | 1 - .../Projects/GetProgrammeEndpoint.cs | 36 +++++++ .../Projects/GetProgrammeProjectsEndpoint.cs | 37 ++++++++ .../Projects/GetProgrammesEndpoint.cs | 35 +++++++ .../Projects/UpdateProgrammeEndpoint.cs | 36 +++++++ .../Abstractions/Services/IProjectService.cs | 10 ++ .../PostFundManagement.Application.csproj | 8 ++ .../Services/Projects/ProjectCacheService.cs | 69 ++++++++++++++ .../Services/Projects/ProjectService.cs | 93 +++++++++++++++++++ .../Services/Projects/Records.cs | 4 + .../Services/Shared/IdentityCacheService.cs | 25 +---- 12 files changed, 367 insertions(+), 24 deletions(-) create mode 100644 PostFundManagement.Api/Endpoints/Projects/CreateProgrammeEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Projects/GetProgrammeEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Projects/GetProgrammeProjectsEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Projects/GetProgrammesEndpoint.cs create mode 100644 PostFundManagement.Api/Endpoints/Projects/UpdateProgrammeEndpoint.cs diff --git a/PostFundManagement.Api/Endpoints/Projects/CreateProgrammeEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/CreateProgrammeEndpoint.cs new file mode 100644 index 0000000..7dec5c1 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/CreateProgrammeEndpoint.cs @@ -0,0 +1,37 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Projects; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Projects; + +[ApiVersionTarget(1)] +public sealed class CreateProgrammeEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapPost("api/projects/programme", async (CreateProgrammeRequest request, HttpContext context, + IProjectService service, CancellationToken cancellationToken = default) => + await service.CreateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r when r.HasError() => Results.BadRequest(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Create programme") + .WithName(typeof(CreateProgrammeEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status404NotFound) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); + } +} diff --git a/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs index 77feb4c..013f13e 100644 --- a/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs +++ b/PostFundManagement.Api/Endpoints/Projects/GetMilestonesEndpoint.cs @@ -21,7 +21,6 @@ public class GetMilestonesEndpoint : IEndpoint { 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)) diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProgrammeEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProgrammeEndpoint.cs new file mode 100644 index 0000000..bec2710 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/GetProgrammeEndpoint.cs @@ -0,0 +1,36 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Models; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Projects; + +[ApiVersionTarget(1)] +public sealed class GetProgrammeEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapGet("api/projects/programme/{programmeId}", async (string programmeId, IProjectService service, + CancellationToken cancellationToken = default) => + await service.GetProgrammeAsync(programmeId, cancellationToken) switch + { + { IsSuccess: true, Value: var programme } => Results.Ok(programme), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.Forbid(), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get specific programme") + .WithName(typeof(GetProgrammeEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects) + .CacheOutput(); + } +} diff --git a/PostFundManagement.Api/Endpoints/Projects/GetProgrammeProjectsEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProgrammeProjectsEndpoint.cs new file mode 100644 index 0000000..dd58556 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/GetProgrammeProjectsEndpoint.cs @@ -0,0 +1,37 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Models; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Projects; + +[ApiVersionTarget(1)] +public sealed class GetProgrammeProjectsEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapGet("api/projects/{programmeId}/programme", async (string programmeId, + IProjectService service, CancellationToken cancellationToken = default) => + await service.GetProgrammeProjectsAsync(programmeId, cancellationToken) switch + { + { IsSuccess: true, Value: var projects } => Results.Ok(projects), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.Forbid(), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Get a list of all projects under a programme") + .WithName(typeof(GetProgrammeProjectsEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(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/GetProgrammesEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/GetProgrammesEndpoint.cs new file mode 100644 index 0000000..b0a553b --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/GetProgrammesEndpoint.cs @@ -0,0 +1,35 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Domain; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Models; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Projects; + +[ApiVersionTarget(1)] +public sealed class GetProgrammesEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapGet("api/projects/programme/{page:int}/{pageSize:int}", async (IProjectService service, + int page = 1, int pageSize = 100, CancellationToken cancellationToken = default) => + await service.GetProgrammesAsync(Pagination.Create(page, pageSize), cancellationToken) switch + { + { IsSuccess: true, Value: var programmes } => Results.Ok(programmes), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Get a list of all programmes") + .WithName(typeof(GetProgrammesEndpoint).ToEndpointName()) + .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/UpdateProgrammeEndpoint.cs b/PostFundManagement.Api/Endpoints/Projects/UpdateProgrammeEndpoint.cs new file mode 100644 index 0000000..3b99d81 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Projects/UpdateProgrammeEndpoint.cs @@ -0,0 +1,36 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Projects; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Projects; + +[ApiVersionTarget(1)] +public sealed class UpdateProgrammeEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapPut("api/projects/programme", async (UpdateProgrammeRequest request, HttpContext context, + IProjectService service, CancellationToken cancellationToken = default) => + await service.UpdateProgrammeAsync(request, userId: context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.NotFound(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Update existing programme") + .WithName(typeof(UpdateProgrammeEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status403Forbidden) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Projects); + } +} diff --git a/PostFundManagement.Application/Abstractions/Services/IProjectService.cs b/PostFundManagement.Application/Abstractions/Services/IProjectService.cs index e833517..f99f26d 100644 --- a/PostFundManagement.Application/Abstractions/Services/IProjectService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IProjectService.cs @@ -33,4 +33,14 @@ public interface IProjectService ValueTask> GetActivityAsync(string activityIdHash, CancellationToken cancellationToken = default); ValueTask> GetProjectRisksAsync(string projectIdHash, CancellationToken cancellationToken = default); + + ValueTask CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); + + ValueTask UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default); + + ValueTask> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default); + + ValueTask> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default); + + ValueTask> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default); } diff --git a/PostFundManagement.Application/PostFundManagement.Application.csproj b/PostFundManagement.Application/PostFundManagement.Application.csproj index ed7a66f..f76e2aa 100644 --- a/PostFundManagement.Application/PostFundManagement.Application.csproj +++ b/PostFundManagement.Application/PostFundManagement.Application.csproj @@ -9,6 +9,14 @@ ..\LICENSE + + 1701;1702;AV0029;AV0030; + + + + 1701;1702;AV0029;AV0030; + + diff --git a/PostFundManagement.Application/Services/Projects/ProjectCacheService.cs b/PostFundManagement.Application/Services/Projects/ProjectCacheService.cs index 3a57158..dcab185 100644 --- a/PostFundManagement.Application/Services/Projects/ProjectCacheService.cs +++ b/PostFundManagement.Application/Services/Projects/ProjectCacheService.cs @@ -12,6 +12,9 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach public async ValueTask CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.CreateActivityAsync(request, userId, cancellationToken); + public async ValueTask CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) => + await service.CreateProgrammeAsync(request, userId, cancellationToken); + public async ValueTask CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.CreateProjectAsync(request, userId, cancellationToken); @@ -99,6 +102,69 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach return Result.Ok(cached.FromJson()); })); + public async ValueTask> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + var cacheKey = HashService.ToMd5Hash($"getprogramme-{programmeIdHash}").Value; + + var cached = await cache.GetStringAsync(cacheKey, cancellationToken); + + if(string.IsNullOrWhiteSpace(cached)) + { + var result = await service.GetProgrammeAsync(programmeIdHash, cancellationToken); + + if (result.IsFailed) return result; + + await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken); + + return result.Value; + } + + return Result.Ok(cached!.FromJson()); + })); + + public async ValueTask> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + var cacheKey = HashService.ToMd5Hash($"getprogrammeprojects-{programmeIdHash}").Value; + + var cached = await cache.GetStringAsync(cacheKey, cancellationToken); + + if(string.IsNullOrWhiteSpace(cached)) + { + var result = await service.GetProgrammeProjectsAsync(programmeIdHash, cancellationToken); + + if (result.IsFailed) return result; + + await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken); + + return result.Value; + } + + return Result.Ok(cached.FromJson()); + })); + + public async ValueTask> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + var cacheKey = HashService.ToMd5Hash($"getprogrammes-{pagination.Index}-{pagination.PageSize}-{pagination.Page}").Value; + + var cached = await cache.GetStringAsync(cacheKey, cancellationToken); + + if(string.IsNullOrWhiteSpace(cached)) + { + var result = await service.GetProgrammesAsync(pagination, cancellationToken); + + if (result.IsFailed) return result; + + await cache.SetStringAsync(cacheKey, result.Value.ToJson(), cacheOptions, cancellationToken); + + return result.Value; + } + + return Result.Ok(cached.FromJson()); + })); + public async ValueTask> GetProjectAsync(string projectIdHash, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { @@ -207,6 +273,9 @@ public sealed class ProjectCacheService(ProjectService service, IDistributedCach public async ValueTask UpdateMilestoneAsync(UpdateMilestoneRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.UpdateMilestoneAsync(request, userId, cancellationToken); + public async ValueTask UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) => + await service.UpdateProgrammeAsync(request, userId, cancellationToken); + public async ValueTask UpdateProjectAsync(UpdateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.UpdateProjectAsync(request, userId, cancellationToken); diff --git a/PostFundManagement.Application/Services/Projects/ProjectService.cs b/PostFundManagement.Application/Services/Projects/ProjectService.cs index 22be636..7165ce5 100644 --- a/PostFundManagement.Application/Services/Projects/ProjectService.cs +++ b/PostFundManagement.Application/Services/Projects/ProjectService.cs @@ -284,4 +284,97 @@ public sealed class ProjectService(IDbContextFactory conte return Result.Ok(risks.Select(i => i.Map()).ToArray()); })); + + public async ValueTask CreateProgrammeAsync(CreateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken)) + return Result.Fail(NotFoundError.New); + + if (await context.Programmes.AnyAsync(p => p.Name == request.Name, cancellationToken)) + return Result.Fail(ValidationError.Create("Programme of the same name exists")); + + context.Programmes.Add(new Domain.Entities.Programme + { + OwnedBy = request.OwnerId, + CreatedBy = userId, + CreatedAt = DateTime.UtcNow, + Status = ApprovalStatus.Pending + }); + + return Result.FailIf(await context.SaveChangesAsync(cancellationToken) <= 0, CreateError.New); + })); + + public async ValueTask UpdateProgrammeAsync(UpdateProgrammeRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Users.AnyAsync(u => u.Id == userId, cancellationToken)) + return Result.Fail(NotFoundError.New); + + var changedTotal = await context.Programmes.Where(p => p.Id == request.ProgrammeId) + .ExecuteUpdateAsync(f => f + .SetProperty(u => u.Name, request.Name) + .SetProperty(u => u.OwnedBy, request.OwnerId) + .SetProperty(u => u.Status, request.Status) + .SetProperty(u => u.UpdatedBy, userId) + .SetProperty(u => u.UpdatedAt, DateTime.UtcNow), cancellationToken); + + return Result.FailIf(changedTotal <= 0, UpdateError.New); + })); + + public async ValueTask> GetProgrammeAsync(string programmeIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + var decodeResult = hashService.DecodeLongIdHash(programmeIdHash); + + if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var programme = await context.Programmes.FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken); + + return programme is not null + ? Result.Ok(programme.Map()) + : Result.Fail(NotFoundError.New); + })); + + public async ValueTask> GetProgrammesAsync(Pagination pagination, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var programmes = await context.Programmes.AsNoTracking() + .OrderByDescending(m => m.CreatedAt) + .Skip((pagination.Page - 1) * pagination.PageSize) + .Take(pagination.PageSize) + .ToListAsync(cancellationToken); + + return Result.Ok(programmes.Select(i => i.Map()).ToArray()); + })); + + public async ValueTask> GetProgrammeProjectsAsync(string programmeIdHash, CancellationToken cancellationToken = default) => + await Result.Try((Func>>)(async () => + { + var decodeResult = hashService.DecodeLongIdHash(programmeIdHash); + + if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create() + .CausedBy(decodeResult.Errors)); + + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + if (!await context.Programmes.AnyAsync(p => p.Id == decodeResult.Value, cancellationToken)) + return Result.Fail(NotFoundError.New); + + var projects = await context.Projects.AsNoTracking() + .Where(p => p.ProgrammeId == decodeResult.Value) + .OrderByDescending(o => o.CreatedAt) + .ToListAsync(cancellationToken); + + return Result.Ok(projects.Select(i => i.Map()).ToArray()); + })); } diff --git a/PostFundManagement.Application/Services/Projects/Records.cs b/PostFundManagement.Application/Services/Projects/Records.cs index 10a9b09..5607b46 100644 --- a/PostFundManagement.Application/Services/Projects/Records.cs +++ b/PostFundManagement.Application/Services/Projects/Records.cs @@ -2,6 +2,10 @@ namespace PostFundManagement.Application.Services.Projects; +public record UpdateProgrammeRequest(long ProgrammeId, string Name, Guid OwnerId, ApprovalStatus Status); + +public record CreateProgrammeRequest(string Name, Guid OwnerId); + public record UpdateProjectRequest(long ProjectId, string Name, string Description, DateTime StartedAt, DateTime? EndedAt, IndustrySector Sector, ProjectType ProjectType); public record UpdateMilestoneRequest(long MilestoneId, DateTime DueAt, string Name, Priority Priority, ApprovalStatus Status); diff --git a/PostFundManagement.Application/Services/Shared/IdentityCacheService.cs b/PostFundManagement.Application/Services/Shared/IdentityCacheService.cs index dbf5ea7..d69eb62 100644 --- a/PostFundManagement.Application/Services/Shared/IdentityCacheService.cs +++ b/PostFundManagement.Application/Services/Shared/IdentityCacheService.cs @@ -1,31 +1,10 @@ using PostFundManagement.Application.Abstractions; using PostFundManagement.Application.Abstractions.Services; -using PostFundManagement.Domain.Errors; namespace PostFundManagement.Application.Services.Shared; -public sealed class IdentityCacheService(IdentityService service, IDistributedCache cache) : ServiceCacheBase, IIdentityService +public sealed class IdentityCacheService(IdentityService service) : ServiceCacheBase, IIdentityService { public async ValueTask UserIsAuthorisedAsync(Guid userId, CancellationToken cancellationToken = default) => - await Result.Try((Func>)(async () => - { - var cacheKey = HashService.ToMd5Hash($"userisauthorised-{userId}").Value; - - var cached = await cache.GetStringAsync(cacheKey); - - if (string.IsNullOrWhiteSpace(cached)) - { - var result = await service.UserIsAuthorisedAsync(userId, cancellationToken); ; - - if (result.IsFailed) return result; - - await cache.SetStringAsync(cacheKey, "true", cacheOptions, cancellationToken); - - return Result.Ok(); - } - - return cached.Equals("true", StringComparison.OrdinalIgnoreCase) - ? Result.Ok() - : Result.Fail(NotFoundError.New); - })); + await service.UserIsAuthorisedAsync(userId, cancellationToken); }