-- ============================================================ -- MulliganMates — Initial Schema -- ============================================================ -- Profiles (extends auth.users) create table public.profiles ( id uuid primary key references auth.users on delete cascade, display_name text not null, avatar_url text, handicap_index numeric(4,1) check (handicap_index between -10 and 54), created_at timestamptz not null default now() ); -- Automatically create a profile when a new user is confirmed create or replace function public.handle_new_user() returns trigger language plpgsql security definer set search_path = public as $$ begin insert into public.profiles (id, display_name) values (new.id, coalesce(new.raw_user_meta_data->>'display_name', split_part(new.email, '@', 1))); return new; end; $$; create trigger on_auth_user_created after insert on auth.users for each row execute procedure public.handle_new_user(); -- ============================================================ -- Courses -- ============================================================ create table public.courses ( id uuid primary key default gen_random_uuid(), name text not null, par int not null check (par between 27 and 90), created_by uuid not null references public.profiles on delete set null, created_at timestamptz not null default now() ); -- Tees per course create table public.tees ( id uuid primary key default gen_random_uuid(), course_id uuid not null references public.courses on delete cascade, name text not null, -- e.g. "Yellow", "White", "Blue" color text not null default '#888888', course_rating numeric(4,1) not null, slope_rating int not null check (slope_rating between 55 and 155), created_at timestamptz not null default now() ); -- Holes (shared across tees on the same course) create table public.holes ( id uuid primary key default gen_random_uuid(), course_id uuid not null references public.courses on delete cascade, hole_number int not null check (hole_number between 1 and 18), par int not null check (par between 3 and 5), stroke_index int not null check (stroke_index between 1 and 18), unique (course_id, hole_number) ); -- Validate that sum of hole pars equals the course par create or replace function public.check_course_par() returns trigger language plpgsql as $$ declare total_par int; course_par int; hole_count int; begin select count(*), sum(h.par) into hole_count, total_par from public.holes h where h.course_id = new.course_id; select c.par into course_par from public.courses c where c.id = new.course_id; -- Only enforce once all holes are entered (9 or 18) if hole_count in (9, 18) then if total_par <> course_par then raise exception 'Sum of hole pars (%) does not equal course par (%)', total_par, course_par; end if; end if; return new; end; $$; create trigger enforce_course_par after insert or update on public.holes for each row execute procedure public.check_course_par(); -- ============================================================ -- Rounds -- ============================================================ create type public.round_status as enum ('lobby', 'active', 'completed'); create table public.rounds ( id uuid primary key default gen_random_uuid(), course_id uuid not null references public.courses, tee_id uuid not null references public.tees, name text, date date not null default current_date, holes_count int not null check (holes_count in (9, 18)), status public.round_status not null default 'lobby', created_by uuid not null references public.profiles, created_at timestamptz not null default now() ); -- Players in a round create table public.round_players ( id uuid primary key default gen_random_uuid(), round_id uuid not null references public.rounds on delete cascade, user_id uuid not null references public.profiles on delete cascade, role text not null default 'player' check (role in ('admin', 'player')), handicap_index numeric(4,1), course_handicap int, joined_at timestamptz not null default now(), unique (round_id, user_id) ); -- Round invitations create table public.round_invites ( id uuid primary key default gen_random_uuid(), round_id uuid not null references public.rounds on delete cascade, email text not null, invited_by uuid not null references public.profiles, accepted_at timestamptz, expires_at timestamptz not null default (now() + interval '7 days'), created_at timestamptz not null default now() ); -- ============================================================ -- Scores -- ============================================================ create table public.scores ( id uuid primary key default gen_random_uuid(), round_id uuid not null references public.rounds on delete cascade, hole_id uuid not null references public.holes on delete cascade, player_id uuid not null references public.profiles on delete cascade, strokes int not null check (strokes > 0), updated_by uuid not null references public.profiles, updated_at timestamptz not null default now(), unique (round_id, hole_id, player_id) ); -- Update updated_at on score change create or replace function public.set_score_updated_at() returns trigger language plpgsql as $$ begin new.updated_at = now(); return new; end; $$; create trigger scores_updated_at before update on public.scores for each row execute procedure public.set_score_updated_at();