diff --git a/API.md b/API.md index 06f581d..ee15c16 100644 --- a/API.md +++ b/API.md @@ -34,6 +34,7 @@ GET /api/results — leaderboard with totals, counts, averages, caller’s vote, ## Admin (admin auth or admin key) POST /api/admin/results — `{ resultsOpen: bool }` locks/unlocks results and aligns player phases GET /api/admin/vote-status — readiness overview (who finalized) -POST /api/admin/link-suggestions — `{ sourceSuggestionId, targetSuggestionId }`; merges vote groups during Vote, clears votes in the linked group, unfinalizes affected players +POST /api/admin/link-suggestions — `{ sourceSuggestionId, targetSuggestionId }`; merges vote groups during Vote, clears votes in the linked group, unfinalizes **all** players +POST /api/admin/unlink-suggestions — `{ suggestionId }`; breaks links, clears votes for that group, unfinalizes **all** players POST /api/admin/reset — clear suggestions/votes; keep players; reset phases/vote-final flags POST /api/admin/factory-reset — wipe players, suggestions, votes, state diff --git a/Endpoints/AdminEndpoints.cs b/Endpoints/AdminEndpoints.cs index 0d05036..cc41351 100644 --- a/Endpoints/AdminEndpoints.cs +++ b/Endpoints/AdminEndpoints.cs @@ -152,14 +152,9 @@ public static class AdminEndpoints await db.SaveChangesAsync(); - var affectedPlayerIds = await db.Votes.Where(v => affectedIds.Contains(v.SuggestionId)).Select(v => v.PlayerId).Distinct().ToListAsync(); - await db.Votes.Where(v => affectedIds.Contains(v.SuggestionId)).ExecuteDeleteAsync(); - if (affectedPlayerIds.Count > 0) - { - await db.Players.Where(p => affectedPlayerIds.Contains(p.Id)).ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false)); - } + await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false)); await tx.CommitAsync(); @@ -167,7 +162,7 @@ public static class AdminEndpoints { RootId = targetRoot, LinkedSuggestionIds = affectedIds, - UnfinalizedPlayers = affectedPlayerIds.Count + UnfinalizedPlayers = await db.Players.CountAsync() }); }); @@ -209,21 +204,16 @@ public static class AdminEndpoints await db.SaveChangesAsync(); - var affectedPlayerIds = await db.Votes.Where(v => groupIds.Contains(v.SuggestionId)).Select(v => v.PlayerId).Distinct().ToListAsync(); - await db.Votes.Where(v => groupIds.Contains(v.SuggestionId)).ExecuteDeleteAsync(); - if (affectedPlayerIds.Count > 0) - { - await db.Players.Where(p => affectedPlayerIds.Contains(p.Id)).ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false)); - } + await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false)); await tx.CommitAsync(); return Results.Ok(new { UnlinkedSuggestionIds = groupIds, - UnfinalizedPlayers = affectedPlayerIds.Count + UnfinalizedPlayers = await db.Players.CountAsync() }); }); diff --git a/FAQ-de.md b/FAQ-de.md index c229ee9..e5c0d10 100644 --- a/FAQ-de.md +++ b/FAQ-de.md @@ -133,7 +133,7 @@ Wenn ein Admin doppelte Spiele verknüpft: Mit **„Finalisieren"** werden deine Bewertungen gesperrt. Deaktiviere es, um erneut zu bearbeiten. -„Finalisieren" ist nur während der Abstimmungsphase verfügbar und wird automatisch zurückgesetzt, wenn: +„Finalisieren" ist nur während der Abstimmungsphase verfügbar und wird für **alle** automatisch zurückgesetzt, wenn: - Ein Joker ein neues Spiel hinzufügt - Ein Admin Spiele verknüpft oder trennt @@ -141,7 +141,7 @@ Mit **„Finalisieren"** werden deine Bewertungen gesperrt. Deaktiviere es, um e Wenn neue Spiele hinzugefügt oder Verknüpfungen geändert werden: - Betroffene Stimmen werden gelöscht - - Deine Abstimmung wird automatisch zurückgesetzt + - Alle Abstimmungen werden automatisch zurückgesetzt Überprüfe deine Liste und bewerte erneut, bevor du wieder finalisierst. diff --git a/FAQ-en.md b/FAQ-en.md index 7338651..9cbb3f9 100644 --- a/FAQ-en.md +++ b/FAQ-en.md @@ -133,7 +133,7 @@ If an admin links duplicate games: Toggling **"Finalize"** locks your scores. Toggle it off to edit again. -Finalize is only available during the Vote phase and will automatically reset if: +Finalize is only available during the Vote phase and will automatically reset for **everyone** if: - A joker adds a new game - An admin links or unlinks games @@ -141,7 +141,7 @@ Finalize is only available during the Vote phase and will automatically reset if If new games are added or links are modified: - Affected votes are cleared - - You are automatically unfinalized + - Everyone is automatically unfinalized Review your list and rescore before finalizing again. diff --git a/GameList.Tests/AdminTests.cs b/GameList.Tests/AdminTests.cs index cb01b02..e6a6720 100644 --- a/GameList.Tests/AdminTests.cs +++ b/GameList.Tests/AdminTests.cs @@ -359,6 +359,41 @@ public class AdminTests }); } + [Fact] + public async Task Link_unfinalizes_all_players() + { + await using var factory = new TestWebApplicationFactory(); + var admin = factory.CreateClientWithCookies(); + await admin.RegisterAsync("admin", admin: true); + var p1 = factory.CreateClientWithCookies(); + await p1.RegisterAsync("p1"); + var p2 = factory.CreateClientWithCookies(); + await p2.RegisterAsync("p2"); + + var a = await p1.CreateSuggestionAsync("A"); + var b = await p1.CreateSuggestionAsync("B"); + + await admin.PostAsJsonAsync("/api/me/phase/next", new { }); + await p1.PostAsJsonAsync("/api/me/phase/next", new { }); + await p2.PostAsJsonAsync("/api/me/phase/next", new { }); + + await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true }); + await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true }); + + var link = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new + { + SourceSuggestionId = a, + TargetSuggestionId = b + }); + link.EnsureSuccessStatusCode(); + + await factory.WithDbContextAsync(async db => + { + var players = await db.Players.Where(p => !p.IsAdmin).ToListAsync(); + Assert.All(players, p => Assert.False(p.VotesFinal)); + }); + } + [Fact] public async Task Unlink_not_found_returns_empty_payload() { diff --git a/SPEC.md b/SPEC.md index a589ee0..e62b806 100644 --- a/SPEC.md +++ b/SPEC.md @@ -23,7 +23,7 @@ Help a small Discord group (4–8 players) pick a co-op game via phased flow: - Score each suggestion 0–10 - Players see only their own votes; can finalize/unfinalize their ballot - **Linked games**: admins can link duplicates; linked games share a vote group. Moving a slider on one updates all linked siblings. -- Linking two games clears votes for the linked group and unfinalizes affected players +- Linking or unlinking games clears votes for the linked group and unfinalizes **all** players so ballots can be reviewed again ## Results Phase - Visible only after admin enables results; players auto-advance when opened diff --git a/wwwroot/js/i18n.js b/wwwroot/js/i18n.js index cc42559..3ec5648 100644 --- a/wwwroot/js/i18n.js +++ b/wwwroot/js/i18n.js @@ -508,6 +508,7 @@ No. Suggestions and votes are read-only. Contact an admin for assistance. - Grant jokers during Vote - Link or unlink duplicate suggestions +- Delete suggestions - View vote readiness (who has finalized) - Delete a player (removes their suggestions and votes) - Reset the database to factory defaults @@ -695,6 +696,7 @@ Nein. Vorschläge und Bewertungen sind schreibgeschützt. Wende dich bei Bedarf - Joker während der Abstimmung vergeben - Doppelte Vorschläge verknüpfen oder trennen +- Vorschläge löschen - Abstimmungsstatus einsehen (wer finalisiert hat) - Einen Spieler löschen (inklusive dessen Vorschläge und Stimmen) - Die Datenbank auf Werkseinstellungen zurücksetzen