Refactor phase reads to pure lookups and align admin docs
This commit is contained in:
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user