Harden owner and suggestion invariants for concurrent writes

This commit is contained in:
2026-02-08 21:37:46 +01:00
parent 569cea161f
commit fe6a9d5da4
13 changed files with 472 additions and 22 deletions

3
API.md
View File

@@ -11,6 +11,7 @@ POST /api/auth/logout
Display names are set during registration and are immutable afterward.
Passwords must be 8-128 chars and contain uppercase, lowercase and number.
The first account created with a valid `adminKey` becomes both `IsAdmin=true` and `IsOwner=true`.
Owner bootstrap is also enforced by a database uniqueness constraint (`IsOwner=true` can only exist once), so concurrent owner registration races fail safely with `400`.
## State (requires auth)
GET /api/state — returns currentPhase (for caller), votesFinal, resultsOpen, updatedAt, counts (players/suggestions/votes)
@@ -26,11 +27,13 @@ POST /api/suggestions — create (name required ≤100; max 5 per player; valida
PUT /api/suggestions/{id} — update (non-admin: own suggestion; title locked after Suggest)
DELETE /api/suggestions/{id} — delete (non-admin only in Suggest; admin any time)
GET /api/suggestions/all — all suggestions (from Vote onward), includes author, link metadata
Suggestion limit is enforced in both app logic and DB trigger; concurrent writes that exceed limit return `400`.
## Votes (requires auth + Vote phase)
GET /api/votes/mine
POST /api/votes — upsert vote; if suggestion is in a linked group, applies the same score to all linked siblings
POST /api/votes/finalize — `{ final: bool }` toggles callers finalized status (blocks further vote edits when true)
Vote upsert includes conflict handling for concurrent writes against the unique `(PlayerId, SuggestionId)` index.
## Results (requires auth + Results phase + resultsOpen)
GET /api/results — leaderboard with totals, counts, averages, callers vote, media/links, link metadata