From c336c675e2a00df6b3be11d4cf0d3ee5dccff92d Mon Sep 17 00:00:00 2001 From: Rolf Date: Wed, 18 Mar 2026 13:38:23 +0100 Subject: [PATCH] Initial commit v1.1 --- supabase/migrations/00001_schema.sql | 159 --------------------------- supabase/migrations/00002_rls.sql | 155 -------------------------- 2 files changed, 314 deletions(-) delete mode 100644 supabase/migrations/00001_schema.sql delete mode 100644 supabase/migrations/00002_rls.sql diff --git a/supabase/migrations/00001_schema.sql b/supabase/migrations/00001_schema.sql deleted file mode 100644 index 7b84143..0000000 --- a/supabase/migrations/00001_schema.sql +++ /dev/null @@ -1,159 +0,0 @@ --- ============================================================ --- 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(); diff --git a/supabase/migrations/00002_rls.sql b/supabase/migrations/00002_rls.sql deleted file mode 100644 index 7fac646..0000000 --- a/supabase/migrations/00002_rls.sql +++ /dev/null @@ -1,155 +0,0 @@ --- ============================================================ --- MulliganMates — Row Level Security Policies --- ============================================================ - -alter table public.profiles enable row level security; -alter table public.courses enable row level security; -alter table public.tees enable row level security; -alter table public.holes enable row level security; -alter table public.rounds enable row level security; -alter table public.round_players enable row level security; -alter table public.round_invites enable row level security; -alter table public.scores enable row level security; - --- ============================================================ --- Helper: is the current user a member of the given round? --- ============================================================ -create or replace function public.is_round_member(p_round_id uuid) -returns boolean language sql security definer stable as $$ - select exists ( - select 1 from public.round_players - where round_id = p_round_id - and user_id = auth.uid() - ) -$$; - --- ============================================================ --- Profiles --- ============================================================ -create policy "Anyone can read profiles" - on public.profiles for select using (true); - -create policy "Users can update their own profile" - on public.profiles for update using (id = auth.uid()); - --- ============================================================ --- Courses --- ============================================================ -create policy "Authenticated users can read courses" - on public.courses for select using (auth.role() = 'authenticated'); - -create policy "Authenticated users can create courses" - on public.courses for insert with check (auth.role() = 'authenticated'); - -create policy "Course creator can update" - on public.courses for update using (created_by = auth.uid()); - --- ============================================================ --- Tees --- ============================================================ -create policy "Authenticated users can read tees" - on public.tees for select using (auth.role() = 'authenticated'); - -create policy "Authenticated users can manage tees on their courses" - on public.tees for all using ( - exists ( - select 1 from public.courses - where id = tees.course_id - and created_by = auth.uid() - ) - ); - --- ============================================================ --- Holes --- ============================================================ -create policy "Authenticated users can read holes" - on public.holes for select using (auth.role() = 'authenticated'); - -create policy "Course creator can manage holes" - on public.holes for all using ( - exists ( - select 1 from public.courses - where id = holes.course_id - and created_by = auth.uid() - ) - ); - --- ============================================================ --- Rounds --- ============================================================ -create policy "Round members can read round" - on public.rounds for select using (public.is_round_member(id)); - -create policy "Authenticated users can create rounds" - on public.rounds for insert with check ( - auth.role() = 'authenticated' and created_by = auth.uid() - ); - -create policy "Round creator can update round" - on public.rounds for update using (created_by = auth.uid()); - --- ============================================================ --- Round Players --- ============================================================ -create policy "Round members can read player list" - on public.round_players for select using (public.is_round_member(round_id)); - -create policy "Round creator can add players" - on public.round_players for insert with check ( - exists ( - select 1 from public.rounds - where id = round_players.round_id - and created_by = auth.uid() - and status = 'lobby' - ) - ); - -create policy "Users can join a round via insert of own record" - on public.round_players for insert with check (user_id = auth.uid()); - --- ============================================================ --- Round Invites --- ============================================================ -create policy "Round members can read invites" - on public.round_invites for select using (public.is_round_member(round_id)); - -create policy "Round creator can send invites" - on public.round_invites for insert with check ( - exists ( - select 1 from public.rounds - where id = round_invites.round_id - and created_by = auth.uid() - and status = 'lobby' - ) - ); - -create policy "Invited user can mark invite accepted" - on public.round_invites for update using ( - email = (select email from auth.users where id = auth.uid()) - ); - --- ============================================================ --- Scores --- ============================================================ -create policy "Round members can read scores" - on public.scores for select using (public.is_round_member(round_id)); - -create policy "Round members can enter and edit scores" - on public.scores for insert with check ( - public.is_round_member(round_id) and - exists ( - select 1 from public.rounds - where id = scores.round_id - and status = 'active' - ) - ); - -create policy "Round members can update scores" - on public.scores for update using ( - public.is_round_member(round_id) and - exists ( - select 1 from public.rounds - where id = scores.round_id - and status = 'active' - ) - );