209 lines
8.4 KiB
PL/PgSQL
209 lines
8.4 KiB
PL/PgSQL
-- OYS Borrow a Boat — Supabase Schema
|
|
-- Run this in the Supabase SQL editor to initialize the database.
|
|
|
|
-- ============================================================
|
|
-- BOATS
|
|
-- ============================================================
|
|
create table public.boats (
|
|
id uuid primary key default gen_random_uuid(),
|
|
name text not null,
|
|
display_name text,
|
|
class text,
|
|
year integer,
|
|
img_src text,
|
|
icon_src text,
|
|
booking_available boolean not null default true,
|
|
required_certs text[] not null default '{}',
|
|
max_passengers integer not null default 6,
|
|
defects jsonb not null default '[]',
|
|
-- defects shape: [{ type: string, severity: string, description: string, detail?: string }]
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.boats enable row level security;
|
|
-- Admins/boatswains can manage boats; all authenticated users can read
|
|
create policy "Authenticated users can read boats" on public.boats
|
|
for select using (auth.role() = 'authenticated');
|
|
create policy "Admins can manage boats" on public.boats
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role in ('admin', 'boatswain')
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- MEMBERS (linked to Supabase auth.users)
|
|
-- ============================================================
|
|
create table public.members (
|
|
id uuid primary key default gen_random_uuid(),
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
first_name text not null default '',
|
|
last_name text not null default '',
|
|
email text not null,
|
|
slack_id text,
|
|
certifications text[] not null default '{}',
|
|
-- cert codes match boats.required_certs values (e.g. 'j27', 'capri25')
|
|
role text not null default 'member'
|
|
check (role in ('member', 'skipper', 'admin', 'boatswain', 'volunteer', 'instructor')),
|
|
created_at timestamptz not null default now(),
|
|
unique(user_id)
|
|
);
|
|
|
|
alter table public.members enable row level security;
|
|
-- Users can read their own record; admins can read all
|
|
create policy "Users can read own member record" on public.members
|
|
for select using (user_id = auth.uid());
|
|
create policy "Admins can read all members" on public.members
|
|
for select using (
|
|
exists (
|
|
select 1 from public.members m2
|
|
where m2.user_id = auth.uid() and m2.role in ('admin', 'boatswain', 'instructor')
|
|
)
|
|
);
|
|
create policy "Users can update own member record" on public.members
|
|
for update using (user_id = auth.uid());
|
|
create policy "Admins can manage all members" on public.members
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members m2
|
|
where m2.user_id = auth.uid() and m2.role = 'admin'
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- INTERVAL TEMPLATES
|
|
-- ============================================================
|
|
create table public.interval_templates (
|
|
id uuid primary key default gen_random_uuid(),
|
|
name text not null,
|
|
time_tuples jsonb not null default '[]',
|
|
-- shape: [[startHHMM, endHHMM], ...] e.g. [["08:00","12:00"],["13:00","17:00"]]
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.interval_templates enable row level security;
|
|
create policy "Authenticated users can read interval templates" on public.interval_templates
|
|
for select using (auth.role() = 'authenticated');
|
|
create policy "Admins can manage interval templates" on public.interval_templates
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role in ('admin', 'boatswain')
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- INTERVALS (available time slots per boat)
|
|
-- ============================================================
|
|
create table public.intervals (
|
|
id uuid primary key default gen_random_uuid(),
|
|
boat_id uuid not null references public.boats(id) on delete cascade,
|
|
start_time timestamptz not null,
|
|
end_time timestamptz not null,
|
|
user_id uuid references auth.users(id) on delete set null,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create index intervals_boat_id_idx on public.intervals(boat_id);
|
|
create index intervals_time_range_idx on public.intervals(start_time, end_time);
|
|
|
|
alter table public.intervals enable row level security;
|
|
create policy "Authenticated users can read intervals" on public.intervals
|
|
for select using (auth.role() = 'authenticated');
|
|
create policy "Admins can manage intervals" on public.intervals
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role in ('admin', 'boatswain')
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- RESERVATIONS
|
|
-- ============================================================
|
|
create table public.reservations (
|
|
id uuid primary key default gen_random_uuid(),
|
|
boat_id uuid not null references public.boats(id) on delete cascade,
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
start_time timestamptz not null,
|
|
end_time timestamptz not null,
|
|
status text not null default 'pending'
|
|
check (status in ('pending', 'tentative', 'confirmed')),
|
|
reason text not null default '',
|
|
comment text not null default '',
|
|
member_ids text[] not null default '{}',
|
|
guest_ids text[] not null default '{}',
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
create index reservations_boat_id_idx on public.reservations(boat_id);
|
|
create index reservations_user_id_idx on public.reservations(user_id);
|
|
create index reservations_time_range_idx on public.reservations(start_time, end_time);
|
|
|
|
alter table public.reservations enable row level security;
|
|
create policy "Users can read own reservations" on public.reservations
|
|
for select using (user_id = auth.uid());
|
|
create policy "Admins can read all reservations" on public.reservations
|
|
for select using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role in ('admin', 'boatswain')
|
|
)
|
|
);
|
|
create policy "Authenticated users can read non-private reservation slots" on public.reservations
|
|
-- Allows seeing start/end/boat_id for overlap checking without exposing user details
|
|
for select using (auth.role() = 'authenticated');
|
|
create policy "Users can create own reservations" on public.reservations
|
|
for insert with check (user_id = auth.uid());
|
|
create policy "Users can update own reservations" on public.reservations
|
|
for update using (user_id = auth.uid());
|
|
create policy "Admins can manage all reservations" on public.reservations
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role in ('admin', 'boatswain')
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- REFERENCE DOCUMENTS
|
|
-- ============================================================
|
|
create table public.reference_docs (
|
|
id uuid primary key default gen_random_uuid(),
|
|
title text not null,
|
|
category text not null,
|
|
tags text[] not null default '{}',
|
|
subtitle text,
|
|
content text not null,
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.reference_docs enable row level security;
|
|
create policy "Authenticated users can read reference docs" on public.reference_docs
|
|
for select using (auth.role() = 'authenticated');
|
|
create policy "Admins can manage reference docs" on public.reference_docs
|
|
for all using (
|
|
exists (
|
|
select 1 from public.members
|
|
where user_id = auth.uid() and role = 'admin'
|
|
)
|
|
);
|
|
|
|
-- ============================================================
|
|
-- SEED: create member record on first sign-in (via trigger)
|
|
-- ============================================================
|
|
create or replace function public.handle_new_user()
|
|
returns trigger language plpgsql security definer as $$
|
|
begin
|
|
insert into public.members (user_id, email)
|
|
values (new.id, new.email)
|
|
on conflict (user_id) do nothing;
|
|
return new;
|
|
end;
|
|
$$;
|
|
|
|
create trigger on_auth_user_created
|
|
after insert on auth.users
|
|
for each row execute procedure public.handle_new_user();
|