Refactored endpoints to be more functional and more expressive

This commit is contained in:
2026-09-01 11:23:25 +02:00
parent 346cb8da5f
commit 2df5b5c2ed
86 changed files with 1565 additions and 1575 deletions
@@ -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<ApplicationDbContext> contextFactory, HashService hashService) : IService
{
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Contract[]>>>)(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<ApplicationDbContext> 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<Result<Contract>> GetContractByIdAsync(string contractIdHash, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Contract>> GetContractAsync(string contractIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Contract>>>)(async () =>
{
var decodeResult = hashService.DecodeLongIdHash(contractIdHash);
if (decodeResult.IsFailed)
return Result.Fail(decodeResult.Errors);
return Result.Fail<Contract>(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<Contract>(NotFoundError.New);
}));
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Budget[]>>>)(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<ApplicationDbContext> 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<Result<Budget>> GetBudgetByIdAsync(string budgetIdHash, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Budget>> GetBudgetAsync(string budgetIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Budget>>>)(async () =>
{
var decodeResult = hashService.DecodeLongIdHash(budgetIdHash);
if (decodeResult.IsFailed)
return Result.Fail(decodeResult.Errors);
return Result.Fail<Budget>(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<Budget>(NotFoundError.New);
}));
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Award[]>>>)(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<ApplicationDbContext> 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<Result<Award>> GetAwardByIdAsync(string awardIdHash, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result<Award>> GetAwardAsync(string awardIdHash, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result<Award>>>)(async () =>
{
var decodeResult = hashService.DecodeLongIdHash(awardIdHash);
if (decodeResult.IsFailed)
return Result.Fail(decodeResult.Errors);
return Result.Fail<Award>(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<Award>(NotFoundError.New);
}));
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(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<ApplicationDbContext> 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<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(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<ApplicationDbContext> 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<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default)
{
try
public async ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(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<ApplicationDbContext> 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<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default) =>
await Result.Try((Func<ValueTask<Result>>)(async () =>
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
public async ValueTask<Result> 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<ApplicationDbContext> 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);
}));
}