Files
game-scoreboard/docs/DATA_MODEL.md
T
joshuaandClaude Opus 5.5 c27a3890f8 Phase 12: share an event with co-hosts
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>
2026-10-05 10:14:29 +02:00

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

  • scores unique on (team_id, criterion_id, judge_id) with NULLS 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_at set; paused = timer_remaining_ms set; reset = both null (full timer_duration_ms).
  • Buzzers (0003_buzzer.sql): buzzes is unique on (game_id, team_id) — one buzz per team per question; buzzed_at defaults to clock_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_token is a 128-bit token like the others; duplicating an event gives the copied teams new ones.
  • Buzzer answers (0004_buzz_result.sql): buzzes.result is the host's mark. The team answering is the earliest buzz with result IS NULL, unless some buzz is correct (question decided). The point for a correct answer is not stored on the buzz: it is +1 on the admin's scores row 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 editing event_cohosts stay owner-only. A trigger stops events.owner_id from changing.
  • frozen_snapshot holds the ScoreboardPayload captured at freeze time.