diff --git a/AGENTS.md b/AGENTS.md index e859503..e177fec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,7 @@ Do not introduce MVC controllers, Razor Pages, Blazor, or SPA frameworks. 6. Vote phase (blind scoring) 7. Results phase (aggregated leaderboard) 8. Admin controls (phase switch, reset) +9. Factory reset (clear all data including players) for fresh testing/deploy --- @@ -76,4 +77,4 @@ Do not introduce MVC controllers, Razor Pages, Blazor, or SPA frameworks. - Implement API first, UI second - Keep changes small and testable - Prefer clarity over abstraction -- After every iteration, do a git commit with a brief summary of the changes as a commit message. \ No newline at end of file +- After every iteration, do a git commit with a brief summary of the changes as a commit message. diff --git a/API.md b/API.md index e25f55a..58fccdd 100644 --- a/API.md +++ b/API.md @@ -24,3 +24,4 @@ GET /api/results ## Admin POST /api/admin/phase POST /api/admin/reset +POST /api/admin/factory-reset diff --git a/Program.cs b/Program.cs index a8057f0..f2cb8b9 100644 --- a/Program.cs +++ b/Program.cs @@ -308,6 +308,26 @@ admin.MapPost("/reset", async (HttpContext ctx, AppDbContext db, IConfiguration return Results.Ok(new { state.CurrentPhase, state.UpdatedAt }); }); +admin.MapPost("/factory-reset", async (HttpContext ctx, AppDbContext db, IConfiguration config) => +{ + if (!IsAuthorized(ctx, config)) return Results.Unauthorized(); + + await using var tx = await db.Database.BeginTransactionAsync(); + + await db.Votes.ExecuteDeleteAsync(); + await db.Suggestions.ExecuteDeleteAsync(); + await db.Players.ExecuteDeleteAsync(); + await db.AppState.ExecuteDeleteAsync(); + + var fresh = NewAppState(); + db.AppState.Add(fresh); + await db.SaveChangesAsync(); + + await tx.CommitAsync(); + + return Results.Ok(new { fresh.CurrentPhase, fresh.UpdatedAt }); +}); + app.Run(); static async Task GetOrCreatePlayer(HttpContext ctx, AppDbContext db) @@ -350,6 +370,13 @@ static bool IsAuthorized(HttpContext ctx, IConfiguration config) return !string.IsNullOrWhiteSpace(expected) && provided == expected; } +static AppState NewAppState() => new() +{ + Id = 1, + CurrentPhase = Phase.Suggest, + UpdatedAt = DateTimeOffset.UnixEpoch +}; + public record SetNameRequest(string Name); public record SuggestionRequest(string Name, string? Genre, string? Description, string? ScreenshotUrl, string? YoutubeUrl); public record SuggestionDto(int Id, string Name, string? Genre, string? Description, string? ScreenshotUrl, string? YoutubeUrl); diff --git a/scripts/smoke.ps1 b/scripts/smoke.ps1 index 88fc184..8080408 100644 --- a/scripts/smoke.ps1 +++ b/scripts/smoke.ps1 @@ -42,8 +42,8 @@ function Invoke-Json { Write-Host "1) Health check" Invoke-Json -Method GET -Path "/health" | Out-Host -Write-Host "`n2) Admin reset" -Invoke-Json -Method POST -Path "/api/admin/reset" -Headers @{ "X-Admin-Key" = $AdminKey } | Out-Host +Write-Host "`n2) Admin factory reset (clears players, suggestions, votes)" +Invoke-Json -Method POST -Path "/api/admin/factory-reset" -Headers @{ "X-Admin-Key" = $AdminKey } | Out-Host Write-Host "`n3) Set player name" $me = Invoke-Json -Method POST -Path "/api/me/name" -Body @{ name = "SmokeTester" }