The owner shares an event by email; a co-host gets full access inside it but can't delete it or change who it's shared with. Shares are keyed by email so an admin can be added before their first login, and RLS (not the UI) is what enforces the owner-only parts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.9 KiB
2.9 KiB
Data model
Full SQL in supabase/migrations/ (0001_init.sql, then 0002_timer.sql, 0003_buzzer.sql, 0004_buzz_result.sql, 0005_cohosts.sql).
| Table | Purpose | Key columns |
|---|---|---|
events |
A game night | owner_id, scoreboard_token, scoreboard_visibility, frozen_snapshot, current_game_id, reveal_active, reveal_count, timer_visible, timer_duration_ms, timer_remaining_ms, timer_ends_at |
teams |
Teams in an event | name, color (#RRGGBB), sort_order, buzzer_token |
games |
Activities | scoring_mode, points_mode, multiplier, placement_points int[], status, admin_notes, judge_notes, open_token, open_link_enabled, judging_locked, spotlight_team_id, buzzer_enabled |
criteria |
Scoring blocks per game | label, description, input_type, min_value, max_value, step, weight |
judges |
Judges per game | name, token, revoked, source (nominated/open_link) |
scores |
One value per (team, criterion, judge) | judge_id NULL = admin score (direct mode or override) |
adjustments |
Bonus/penalty | points (+/-), reason, optional game_id |
buzzes |
Who buzzed for the current question | game_id, team_id, buzzed_at, result (correct/wrong/NULL) |
event_cohosts |
Admins an event is shared with | event_id, email (lower case), unique together |
Notes
scoresunique on(team_id, criterion_id, judge_id)withNULLS NOT DISTINCT, so there's exactly one admin row per team+criterion → clean upserts.- In judged games, an admin row (
judge_id IS NULL) is an override for that cell. - Deleting a criterion/team/judge cascades its scores.
- Timer (
0002_timer.sql): running =timer_ends_atset; paused =timer_remaining_msset; reset = both null (fulltimer_duration_ms). - Buzzers (
0003_buzzer.sql):buzzesis unique on(game_id, team_id)— one buzz per team per question;buzzed_atdefaults toclock_timestamp()and decides the order. Clearing the buzzers deletes the game's rows. A trigger checks the team is in the game's event.teams.buzzer_tokenis a 128-bit token like the others; duplicating an event gives the copied teams new ones. - Buzzer answers (
0004_buzz_result.sql):buzzes.resultis the host's mark. The team answering is the earliest buzz withresult IS NULL, unless some buzz iscorrect(question decided). The point for a correct answer is not stored on the buzz: it is +1 on the admin'sscoresrow for the game's first criterion. - Co-hosts (
0005_cohosts.sql): a share is keyed by email, not user id, so an admin can be added before they have ever logged in. RLS matches it against the email in the login token.is_event_admin(event)= owner or co-host and guards every table inside an event; deleting the event and editingevent_cohostsstay owner-only. A trigger stopsevents.owner_idfrom changing. frozen_snapshotholds theScoreboardPayloadcaptured at freeze time.