using GerbilManagerWebAPI.Dtos; using GerbilManagerWebAPI.Models; using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.EntityFrameworkCore; namespace GerbilManagerWebAPI.Endpoints { /// /// 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. /// 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, 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, BadRequest>> ( 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, NotFound, BadRequest>> ( 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> ( 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); } }