Unfinalize everyone on suggestion linking changes

This commit is contained in:
2026-02-06 23:31:31 +01:00
parent 5895d66179
commit cfe7a24e12
7 changed files with 48 additions and 20 deletions

3
API.md
View File

@@ -34,6 +34,7 @@ GET /api/results — leaderboard with totals, counts, averages, callers vote,
## Admin (admin auth or admin key) ## Admin (admin auth or admin key)
POST /api/admin/results — `{ resultsOpen: bool }` locks/unlocks results and aligns player phases POST /api/admin/results — `{ resultsOpen: bool }` locks/unlocks results and aligns player phases
GET /api/admin/vote-status — readiness overview (who finalized) 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/reset — clear suggestions/votes; keep players; reset phases/vote-final flags
POST /api/admin/factory-reset — wipe players, suggestions, votes, state POST /api/admin/factory-reset — wipe players, suggestions, votes, state

View File

@@ -152,14 +152,9 @@ public static class AdminEndpoints
await db.SaveChangesAsync(); 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(); await db.Votes.Where(v => affectedIds.Contains(v.SuggestionId)).ExecuteDeleteAsync();
if (affectedPlayerIds.Count > 0) await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
{
await db.Players.Where(p => affectedPlayerIds.Contains(p.Id)).ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
}
await tx.CommitAsync(); await tx.CommitAsync();
@@ -167,7 +162,7 @@ public static class AdminEndpoints
{ {
RootId = targetRoot, RootId = targetRoot,
LinkedSuggestionIds = affectedIds, LinkedSuggestionIds = affectedIds,
UnfinalizedPlayers = affectedPlayerIds.Count UnfinalizedPlayers = await db.Players.CountAsync()
}); });
}); });
@@ -209,21 +204,16 @@ public static class AdminEndpoints
await db.SaveChangesAsync(); 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(); await db.Votes.Where(v => groupIds.Contains(v.SuggestionId)).ExecuteDeleteAsync();
if (affectedPlayerIds.Count > 0) await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
{
await db.Players.Where(p => affectedPlayerIds.Contains(p.Id)).ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
}
await tx.CommitAsync(); await tx.CommitAsync();
return Results.Ok(new return Results.Ok(new
{ {
UnlinkedSuggestionIds = groupIds, UnlinkedSuggestionIds = groupIds,
UnfinalizedPlayers = affectedPlayerIds.Count UnfinalizedPlayers = await db.Players.CountAsync()
}); });
}); });

View File

@@ -133,7 +133,7 @@ Wenn ein Admin doppelte Spiele verknüpft:
Mit **„Finalisieren"** werden deine Bewertungen gesperrt. Deaktiviere es, um erneut zu bearbeiten. 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 Joker ein neues Spiel hinzufügt
- Ein Admin Spiele verknüpft oder trennt - 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: Wenn neue Spiele hinzugefügt oder Verknüpfungen geändert werden:
- Betroffene Stimmen werden gelöscht - 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. Überprüfe deine Liste und bewerte erneut, bevor du wieder finalisierst.

View File

@@ -133,7 +133,7 @@ If an admin links duplicate games:
Toggling **"Finalize"** locks your scores. Toggle it off to edit again. 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 - A joker adds a new game
- An admin links or unlinks games - 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: If new games are added or links are modified:
- Affected votes are cleared - Affected votes are cleared
- You are automatically unfinalized - Everyone is automatically unfinalized
Review your list and rescore before finalizing again. Review your list and rescore before finalizing again.

View File

@@ -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] [Fact]
public async Task Unlink_not_found_returns_empty_payload() public async Task Unlink_not_found_returns_empty_payload()
{ {

View File

@@ -23,7 +23,7 @@ Help a small Discord group (48 players) pick a co-op game via phased flow:
- Score each suggestion 010 - Score each suggestion 010
- Players see only their own votes; can finalize/unfinalize their ballot - 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. - **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 ## Results Phase
- Visible only after admin enables results; players auto-advance when opened - Visible only after admin enables results; players auto-advance when opened

View File

@@ -508,6 +508,7 @@ No. Suggestions and votes are read-only. Contact an admin for assistance.
- Grant jokers during Vote - Grant jokers during Vote
- Link or unlink duplicate suggestions - Link or unlink duplicate suggestions
- Delete suggestions
- View vote readiness (who has finalized) - View vote readiness (who has finalized)
- Delete a player (removes their suggestions and votes) - Delete a player (removes their suggestions and votes)
- Reset the database to factory defaults - 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 - Joker während der Abstimmung vergeben
- Doppelte Vorschläge verknüpfen oder trennen - Doppelte Vorschläge verknüpfen oder trennen
- Vorschläge löschen
- Abstimmungsstatus einsehen (wer finalisiert hat) - Abstimmungsstatus einsehen (wer finalisiert hat)
- Einen Spieler löschen (inklusive dessen Vorschläge und Stimmen) - Einen Spieler löschen (inklusive dessen Vorschläge und Stimmen)
- Die Datenbank auf Werkseinstellungen zurücksetzen - Die Datenbank auf Werkseinstellungen zurücksetzen