Refactor phase reads to pure lookups and align admin docs

This commit is contained in:
2026-02-07 00:36:04 +01:00
parent fcd44de4e4
commit 81c04e0866
12 changed files with 124 additions and 42 deletions

View File

@@ -57,7 +57,7 @@ public static class AdminEndpoints
if (player is null)
return Results.NotFound(new { error = "Player not found." });
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return Results.BadRequest(new { error = "Player must be in the Vote phase to receive a joker." });
@@ -108,7 +108,7 @@ public static class AdminEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -172,7 +172,7 @@ public static class AdminEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -260,3 +260,4 @@ public static class AdminEndpoints
});
}
}

View File

@@ -34,42 +34,53 @@ internal static class EndpointHelpers
return existing;
}
public static async Task<Phase> GetPhase(AppDbContext db, Guid playerId)
public static async Task<Phase> GetCurrentPhaseAsync(AppDbContext db, Guid playerId)
{
var player = await db.Players.FirstOrDefaultAsync(p => p.Id == playerId);
if (player is null)
var playerPhase = await db.Players
.AsNoTracking()
.Where(p => p.Id == playerId)
.Select(p => (Phase?)p.CurrentPhase)
.FirstOrDefaultAsync();
if (playerPhase is null)
return Phase.Suggest;
var state = await db.AppState.FirstAsync();
var resultsOpen = await db.AppState.AsNoTracking().Select(s => s.ResultsOpen).FirstAsync();
return GetCurrentPhase(playerPhase.Value, resultsOpen);
}
public static Phase GetCurrentPhase(Phase phase, bool resultsOpen)
{
var normalized = phase == Phase.Reveal ? Phase.Vote : phase;
if (resultsOpen)
return Phase.Results;
return normalized == Phase.Results ? Phase.Vote : normalized;
}
public static bool ReconcilePlayerPhase(Player player, bool resultsOpen)
{
var changed = false;
// Auto-upgrade any legacy Reveal phase to Vote to avoid blank screens
if (player.CurrentPhase == Phase.Reveal)
{
player.CurrentPhase = Phase.Vote;
changed = true;
}
// Keep phases aligned with results availability
if (state.ResultsOpen && player.CurrentPhase != Phase.Results)
if (resultsOpen && player.CurrentPhase != Phase.Results)
{
player.CurrentPhase = Phase.Results;
changed = true;
}
else if (!state.ResultsOpen && player.CurrentPhase == Phase.Results)
else if (!resultsOpen && player.CurrentPhase == Phase.Results)
{
player.CurrentPhase = Phase.Vote;
player.VotesFinal = false;
changed = true;
}
if (changed)
{
await db.SaveChangesAsync();
}
return player.CurrentPhase;
return changed;
}
public static IResult PhaseMismatch(Phase required, Phase current) =>

View File

@@ -23,7 +23,7 @@ public static class ResultsEndpoints
var appState = await db.AppState.AsNoTracking().FirstAsync();
if (!appState.ResultsOpen)
return Results.BadRequest(new { error = "Results are locked until the admin enables them." });
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Results)
return EndpointHelpers.PhaseMismatch(Phase.Results, phase);
@@ -96,3 +96,4 @@ public static class ResultsEndpoints
);
}
}

View File

@@ -16,9 +16,8 @@ public static class StateEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var state = await db.AppState.AsNoTracking().FirstAsync();
var phase = EndpointHelpers.GetCurrentPhase(player.CurrentPhase, state.ResultsOpen);
var summary = new
{
CurrentPhase = phase,
@@ -39,7 +38,8 @@ public static class StateEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var state = await db.AppState.AsNoTracking().FirstAsync();
var phase = EndpointHelpers.GetCurrentPhase(player.CurrentPhase, state.ResultsOpen);
return Results.Ok(new
{
player.Id,
@@ -52,17 +52,20 @@ public static class StateEndpoints
});
});
group.MapPost("/me/phase/next", async (HttpContext ctx, AppDbContext db, IConfiguration _) =>
group.MapPost("/me/phase/next", async (HttpContext ctx, AppDbContext db) =>
{
var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
if (player is null)
return Results.Unauthorized();
var next = NextPhase(player.CurrentPhase);
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 Results.BadRequest(new { error = "Results are locked until the admin enables them." });
}
@@ -76,7 +79,7 @@ public static class StateEndpoints
});
});
group.MapPost("/me/phase/prev", async (HttpContext ctx, AppDbContext db, IConfiguration _) =>
group.MapPost("/me/phase/prev", async (HttpContext ctx, AppDbContext db) =>
{
var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
if (player is null)
@@ -88,10 +91,11 @@ public static class StateEndpoints
return Results.BadRequest(new { error = "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();
var appState = await db.AppState.AsNoTracking().FirstAsync();
return Results.Ok(new
{
player.CurrentPhase,
@@ -117,3 +121,4 @@ public static class StateEndpoints
_ => Phase.Suggest
};
}

View File

@@ -69,7 +69,7 @@ public static class SuggestEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
var usingJoker = phase == Phase.Vote && player.HasJoker;
if (phase != Phase.Suggest && !usingJoker)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
@@ -121,7 +121,7 @@ public static class SuggestEndpoints
if (!isAdmin)
{
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Suggest)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
}
@@ -181,7 +181,7 @@ public static class SuggestEndpoints
if (suggestion.PlayerId != player!.Id)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase == Phase.Results)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
@@ -244,7 +244,7 @@ public static class SuggestEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase < Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -328,3 +328,4 @@ public static class SuggestEndpoints
return true;
}
}

View File

@@ -19,7 +19,7 @@ public static class VoteEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -43,7 +43,7 @@ public static class VoteEndpoints
if (player.VotesFinal)
return Results.BadRequest(new { error = "Votes are finalized. Unfinalize before changing scores." });
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -97,7 +97,7 @@ public static class VoteEndpoints
if (player is null)
return Results.Unauthorized();
var phase = await EndpointHelpers.GetPhase(db, player.Id);
var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
if (phase != Phase.Vote)
return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);
@@ -107,3 +107,4 @@ public static class VoteEndpoints
});
}
}