Implemented basic endpoints

This commit is contained in:
2026-08-25 17:52:44 +02:00
parent be5fbdbb5f
commit 5a4c621950
43 changed files with 1870 additions and 6 deletions
@@ -0,0 +1,62 @@
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class CreateActivityEndpoint : IEndpoint
{
public record CreateActivityRequest(long? MilestoneId, string Category, decimal Amount, string Title, string Description, DateTime? PerformedAt);
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/{projectId:long}/activities", async (long projectId, CreateActivityRequest request,
ClaimsPrincipal principal, IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
{
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
if (!Guid.TryParse(sidClaim, out var userId))
return Results.BadRequest("Missing or invalid 'sid' claim.");
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Projects.AnyAsync(p => p.Id == projectId, cancellationToken))
return Results.BadRequest($"Project with ID {projectId} does not exist.");
if (request.MilestoneId.HasValue)
if (!await context.Milestones.AnyAsync(m => m.Id == request.MilestoneId.Value, cancellationToken))
return Results.BadRequest($"Milestone with ID {request.MilestoneId.Value} does not exist.");
var activity = new Domain.Entities.Activity
{
ProjectId = projectId,
MilestoneId = request.MilestoneId,
Category = request.Category,
Amount = request.Amount,
Title = request.Title,
Description = request.Description,
PerformedAt = request.PerformedAt,
Status = ApprovalStatus.Pending,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
};
context.Activities.Add(activity);
return await context.SaveChangesAsync(cancellationToken) > 0
? Results.Created($"api/projects/{projectId}/activities/{activity.Id}", activity)
: Results.BadRequest("Failed to create activity");
})
.RequireAuthorization()
.WithDescription("Add a new activity under project (M04)")
.WithName(typeof(CreateActivityEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Entities.Activity>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,56 @@
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class CreateProjectEndpoint : IEndpoint
{
public record CreateProjectRequest(long ProgrammeId, string Name, string Description, IndustrySector Sector, ProjectType ProjectType);
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects", async (CreateProjectRequest request, ClaimsPrincipal principal,
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
{
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
if (!Guid.TryParse(sidClaim, out var userId))
return Results.BadRequest("Missing or invalid 'sid' claim.");
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
return Results.BadRequest($"Programme with ID {request.ProgrammeId} does not exist.");
var project = new Domain.Entities.Project
{
ProgrammeId = request.ProgrammeId,
Name = request.Name,
Description = request.Description,
Sector = request.Sector,
ProjectType = request.ProjectType,
Status = ApprovalStatus.Pending,
CreatedBy = userId,
CreatedAt = DateTime.UtcNow
};
context.Projects.Add(project);
return await context.SaveChangesAsync(cancellationToken) > 0
? Results.Created($"api/projects/{project.Id}", project)
: Results.BadRequest("Failed to create prject");
})
.RequireAuthorization()
.WithDescription("Create a new project (M04)")
.WithName(typeof(CreateProjectEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Entities.Project>(StatusCodes.Status201Created)
.Produces(StatusCodes.Status400BadRequest)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,33 @@
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class GetActivitiesEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{projectId:long}/activities", async (long projectId,
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var activities = await context.Activities.AsNoTracking()
.Where(a => a.ProjectId == projectId)
.OrderByDescending(a => a.CreatedAt)
.ToListAsync(cancellationToken);
return Results.Ok(activities);
})
.RequireAuthorization()
.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);
}
}
@@ -0,0 +1,33 @@
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class GetProjectByIdEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects/{id:long}", async (long id, IDbContextFactory<ApplicationDbContext> contextFactory,
CancellationToken cancellationToken = default) =>
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var project = await context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
return project != null
? Results.Ok(project)
: Results.NotFound();
})
.RequireAuthorization()
.WithDescription("Get project by ID")
.WithName(typeof(GetProjectByIdEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Entities.Project>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,41 @@
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class GetProjectsEndpoint : IEndpoint
{
public void Map(IEndpointRouteBuilder builder)
{
builder.MapGet("api/projects", async (IDbContextFactory<ApplicationDbContext> contextFactory,
int page = 1, int pageSize = 10, CancellationToken cancellationToken = default) =>
{
if (page < 1) page = 1;
if (pageSize < 1) pageSize = 10;
if (pageSize > 100) pageSize = 100;
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var query = context.Projects.AsNoTracking();
var totalCount = await query.CountAsync(cancellationToken);
var items = await query.OrderByDescending(p => p.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync(cancellationToken);
return Results.Ok(new { Items = items, TotalCount = totalCount, Page = page, PageSize = pageSize });
})
.RequireAuthorization()
.WithDescription("Get a list of projects")
.WithName(typeof(GetProjectsEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces(StatusCodes.Status200OK)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}
@@ -0,0 +1,50 @@
using PostFundManagement.Domain;
using PostFundManagement.Domain.Abstractions;
using PostFundManagement.Domain.Api;
using PostFundManagement.Domain.Extensions;
using PostFundManagement.Infrastructure.Database;
namespace PostFundManagement.Api.Endpoints.Projects;
[ApiVersionTarget(1)]
public class UpdateProjectEndpoint : IEndpoint
{
public record UpdateProjectStatusRequest(ApprovalStatus Status);
public void Map(IEndpointRouteBuilder builder)
{
builder.MapPost("api/projects/{id:long}/status", async (long id, UpdateProjectStatusRequest request, ClaimsPrincipal principal,
IDbContextFactory<ApplicationDbContext> contextFactory, CancellationToken cancellationToken = default) =>
{
var sidClaim = principal.FindFirst("sid")?.Value ?? principal.FindFirst(ClaimTypes.Sid)?.Value;
if (!Guid.TryParse(sidClaim, out var userId))
return Results.BadRequest("Missing or invalid 'sid' claim.");
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
if (project == null)
return Results.NotFound($"Project with ID {id} does not exist.");
project.Status = request.Status;
project.UpdatedBy = userId;
project.UpdatedAt = DateTime.UtcNow;
context.Projects.Update(project);
return await context.SaveChangesAsync(cancellationToken) > 0
? Results.Ok(project)
: Results.BadRequest("Failed to update project");
})
.RequireAuthorization()
.WithDescription("Update project status (M04)")
.WithName(typeof(UpdateProjectEndpoint).ToEndpointName())
.MapToApiVersion(new ApiVersion(1))
.Produces<Domain.Entities.Project>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound)
.Produces(StatusCodes.Status401Unauthorized)
.WithTags(EndpointTags.Projects);
}
}