Merge branch 'worktree-agent-ae59040b377a324cb'
# Conflicts: # GerbilManagerWebAPI/ApplicationContext.cs # GerbilManagerWebAPI/Program.cs # gerbil-manager-web/e2e/mock-data.ts # gerbil-manager-web/src/pages/GerbilDetailPage.tsx # gerbil-manager-web/src/strings/de.ts
This commit is contained in:
114
GerbilManagerWebAPI/Endpoints/ExhibitionEndpoints.cs
Normal file
114
GerbilManagerWebAPI/Endpoints/ExhibitionEndpoints.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using GerbilManagerWebAPI.Dtos;
|
||||
using GerbilManagerWebAPI.Models;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace GerbilManagerWebAPI.Endpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// EXHIBITION: show/exhibition results & awards per animal (RennmausPro `ausz_tb`).
|
||||
/// GET /exhibitions[?gerbilId=…] -> list (optionally filtered to one animal), newest first.
|
||||
/// POST /exhibitions -> create a result, returns 201.
|
||||
/// PUT /exhibitions/{id} -> edit a result (partial), 404 on missing id.
|
||||
/// DELETE /exhibitions/{id} -> remove a result, 404 on missing id.
|
||||
/// Decoupled from gerbils (loose nullable GerbilId, no FK) so rows survive the import re-ingest wipe.
|
||||
/// </summary>
|
||||
public static class ExhibitionEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapExhibitionEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/exhibitions").WithTags("Exhibitions");
|
||||
|
||||
group.MapGet("/", async (ApplicationContext db, Guid? gerbilId) =>
|
||||
{
|
||||
// Filter in the query; order in memory (SQLite test host cannot ORDER BY a
|
||||
// DateTimeOffset column — mirrors FeedbackEndpoints).
|
||||
var query = db.ExhibitionResults.AsNoTracking();
|
||||
if (gerbilId is { } gid)
|
||||
query = query.Where(x => x.GerbilId == gid);
|
||||
var rows = await query.ToListAsync();
|
||||
return TypedResults.Ok(rows
|
||||
.OrderByDescending(x => x.Date ?? DateTime.MinValue)
|
||||
.ThenByDescending(x => x.CreatedAt)
|
||||
.Select(ToDto)
|
||||
.ToList());
|
||||
});
|
||||
|
||||
group.MapGet("/{id:guid}", async Task<Results<Ok<ExhibitionResultDto>, NotFound>> (
|
||||
Guid id, ApplicationContext db) =>
|
||||
{
|
||||
var r = await db.ExhibitionResults.AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);
|
||||
return r is null ? TypedResults.NotFound() : TypedResults.Ok(ToDto(r));
|
||||
});
|
||||
|
||||
group.MapPost("/", async Task<Results<Created<ExhibitionResultDto>, BadRequest<string>>> (
|
||||
ExhibitionResultInput input, ApplicationContext db) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.EventName))
|
||||
return TypedResults.BadRequest("EventName darf nicht leer sein.");
|
||||
|
||||
var entity = new ExhibitionResult
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GerbilId = input.GerbilId,
|
||||
EntityName = Trim(input.EntityName),
|
||||
EventName = input.EventName.Trim(),
|
||||
Date = input.Date,
|
||||
Placement = Trim(input.Placement),
|
||||
Award = Trim(input.Award),
|
||||
Note = Trim(input.Note),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
db.ExhibitionResults.Add(entity);
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.Created($"/exhibitions/{entity.Id}", ToDto(entity));
|
||||
});
|
||||
|
||||
group.MapPut("/{id:guid}", async Task<Results<Ok<ExhibitionResultDto>, NotFound, BadRequest<string>>> (
|
||||
Guid id, ExhibitionResultUpdate input, ApplicationContext db) =>
|
||||
{
|
||||
var entity = await db.ExhibitionResults.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (entity is null)
|
||||
return TypedResults.NotFound();
|
||||
|
||||
if (input.EventName is not null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input.EventName))
|
||||
return TypedResults.BadRequest("EventName darf nicht leer sein.");
|
||||
entity.EventName = input.EventName.Trim();
|
||||
}
|
||||
if (input.GerbilId is not null) entity.GerbilId = input.GerbilId;
|
||||
if (input.EntityName is not null) entity.EntityName = Trim(input.EntityName);
|
||||
if (input.Date is not null) entity.Date = input.Date;
|
||||
if (input.Placement is not null) entity.Placement = Trim(input.Placement);
|
||||
if (input.Award is not null) entity.Award = Trim(input.Award);
|
||||
if (input.Note is not null) entity.Note = Trim(input.Note);
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.Ok(ToDto(entity));
|
||||
});
|
||||
|
||||
group.MapDelete("/{id:guid}", async Task<Results<NoContent, NotFound>> (
|
||||
Guid id, ApplicationContext db) =>
|
||||
{
|
||||
var entity = await db.ExhibitionResults.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (entity is null)
|
||||
return TypedResults.NotFound();
|
||||
|
||||
db.ExhibitionResults.Remove(entity);
|
||||
await db.SaveChangesAsync();
|
||||
return TypedResults.NoContent();
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
// Empty/whitespace optional strings normalise to null.
|
||||
private static string? Trim(string? s) =>
|
||||
string.IsNullOrWhiteSpace(s) ? null : s.Trim();
|
||||
|
||||
private static ExhibitionResultDto ToDto(ExhibitionResult r) =>
|
||||
new(r.Id, r.GerbilId, r.EntityName, r.EventName, r.Date,
|
||||
r.Placement, r.Award, r.Note, r.CreatedAt);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user