Files
MulliganMates/supabase/migrations/00001_schema.sql
T
2026-03-18 12:37:17 +01:00

160 lines
5.7 KiB
PL/PgSQL

-- ============================================================
-- 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();