using GameList.Data; using GameList.Domain; using GameList.Contracts; using Microsoft.EntityFrameworkCore; namespace GameList.Endpoints; public static class StateEndpoints { public static void MapStateEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api").RequireAuthorization(); group.MapGet("/state", async (HttpContext ctx, AppDbContext db) => { var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return EndpointHelpers.UnauthorizedError(); var state = await db.AppState.AsNoTracking().FirstAsync(); var phase = EndpointHelpers.GetCurrentPhase(player.CurrentPhase, state.ResultsOpen); var summary = new StateSummaryResponse( phase, player.VotesFinal, player.HasJoker, state.ResultsOpen, state.UpdatedAt, await db.Players.CountAsync(), await db.Suggestions.CountAsync(), await db.Votes.CountAsync() ); return Results.Ok(summary); }); group.MapGet("/me", async (HttpContext ctx, AppDbContext db) => { var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return EndpointHelpers.UnauthorizedError(); var state = await db.AppState.AsNoTracking().FirstAsync(); var phase = EndpointHelpers.GetCurrentPhase(player.CurrentPhase, state.ResultsOpen); return Results.Ok(new MeResponse( player.Id, player.Username, player.DisplayName, player.IsAdmin, phase, player.VotesFinal, player.HasJoker )); }); group.MapPost("/me/phase/next", async (HttpContext ctx, AppDbContext db) => { var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return EndpointHelpers.UnauthorizedError(); var appState = await db.AppState.FirstAsync(); var reconciled = EndpointHelpers.ReconcilePlayerPhase(player, appState.ResultsOpen); var next = NextPhase(player.CurrentPhase); if (next == Phase.Results && !appState.ResultsOpen) { if (reconciled) await db.SaveChangesAsync(); return EndpointHelpers.BadRequestError("Results are locked until the admin enables them."); } player.CurrentPhase = next; player.VotesFinal = false; // moving forward clears any prior finalize await db.SaveChangesAsync(); return Results.Ok(new PhaseTransitionResponse(player.CurrentPhase, appState.ResultsOpen)); }); group.MapPost("/me/phase/prev", async (HttpContext ctx, AppDbContext db) => { var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db); if (player is null) return EndpointHelpers.UnauthorizedError(); var isAdmin = await EndpointHelpers.IsAdmin(ctx, db); if (!isAdmin) { return EndpointHelpers.BadRequestError("Only admins can move backward."); } var appState = await db.AppState.FirstAsync(); EndpointHelpers.ReconcilePlayerPhase(player, appState.ResultsOpen); player.CurrentPhase = PrevPhase(player.CurrentPhase); player.VotesFinal = false; await db.SaveChangesAsync(); return Results.Ok(new PhaseTransitionResponse(player.CurrentPhase, appState.ResultsOpen)); }); } private static Phase NextPhase(Phase current) => current switch { Phase.Suggest => Phase.Vote, Phase.Reveal => Phase.Vote, // legacy safety Phase.Vote => Phase.Results, _ => Phase.Results }; private static Phase PrevPhase(Phase current) => current switch { Phase.Results => Phase.Vote, Phase.Vote => Phase.Suggest, Phase.Reveal => Phase.Suggest, // legacy safety _ => Phase.Suggest }; }