| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788 |
- -- 00001_initial_schema.sql
- -- MovieDice: initial database schema
- -- Tables: users, groups, group_members, movies, landing_reel_posters
- -- =============================================================================
- -- users
- -- =============================================================================
- CREATE TABLE public.users (
- id uuid PRIMARY KEY, -- maps to auth.users.id (Supabase Anonymous Sign-In)
- display_name text NOT NULL
- CONSTRAINT users_display_name_length CHECK (char_length(display_name) BETWEEN 1 AND 30)
- CONSTRAINT users_display_name_no_html CHECK (display_name !~ '[<>]')
- CONSTRAINT users_display_name_no_control CHECK (display_name !~ '[\x00-\x1F\x7F]'),
- avatar_color text
- CONSTRAINT users_avatar_color_hex CHECK (avatar_color IS NULL OR avatar_color ~ '^#[0-9a-fA-F]{6}$'),
- recovery_code text, -- Argon2id hashed, nullable, single-use
- last_active_at timestamptz NOT NULL DEFAULT now(),
- created_at timestamptz NOT NULL DEFAULT now()
- );
- -- =============================================================================
- -- groups
- -- =============================================================================
- CREATE TABLE public.groups (
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
- name text NOT NULL
- CONSTRAINT groups_name_length CHECK (char_length(name) BETWEEN 1 AND 50)
- CONSTRAINT groups_name_no_html CHECK (name !~ '[<>]')
- CONSTRAINT groups_name_no_control CHECK (name !~ '[\x00-\x1F\x7F]'),
- invite_code text NOT NULL UNIQUE,
- created_by uuid NOT NULL REFERENCES public.users(id),
- created_at timestamptz NOT NULL DEFAULT now()
- );
- -- Note: invite_code already indexed by the UNIQUE constraint
- -- =============================================================================
- -- group_members
- -- =============================================================================
- CREATE TABLE public.group_members (
- group_id uuid NOT NULL REFERENCES public.groups(id) ON DELETE CASCADE,
- user_id uuid NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
- role text NOT NULL DEFAULT 'member'
- CONSTRAINT group_members_role_valid CHECK (role IN ('admin', 'member')),
- joined_at timestamptz NOT NULL DEFAULT now(),
- PRIMARY KEY (group_id, user_id)
- );
- CREATE INDEX idx_group_members_user_id ON public.group_members (user_id);
- -- Note: group_id already indexed as leading column of the composite PK
- -- =============================================================================
- -- movies
- -- =============================================================================
- CREATE TABLE public.movies (
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
- group_id uuid NOT NULL REFERENCES public.groups(id) ON DELETE CASCADE,
- tmdb_id integer NOT NULL,
- title text NOT NULL,
- year integer,
- poster_path text,
- genres text[],
- trailer_url text
- CONSTRAINT movies_trailer_url_domain CHECK (
- trailer_url IS NULL
- OR trailer_url ~ '^https://(www\.)?(youtube\.com|themoviedb\.org|imdb\.com)/'
- ),
- trailer_url_refreshed_at timestamptz,
- metadata_refreshed_at timestamptz,
- added_by uuid REFERENCES public.users(id) ON DELETE SET NULL,
- watched boolean NOT NULL DEFAULT false,
- watched_at timestamptz,
- added_at timestamptz NOT NULL DEFAULT now()
- );
- CREATE INDEX idx_movies_group_id ON public.movies (group_id);
- CREATE INDEX idx_movies_added_by ON public.movies (added_by);
- -- =============================================================================
- -- landing_reel_posters
- -- =============================================================================
- CREATE TABLE public.landing_reel_posters (
- id serial PRIMARY KEY,
- tmdb_id integer NOT NULL,
- poster_path text NOT NULL,
- title text NOT NULL,
- refreshed_at timestamptz NOT NULL DEFAULT now()
- );
|