Initial project scaffolding
This commit is contained in:
208
supabase/schema.sql
Normal file
208
supabase/schema.sql
Normal file
@@ -0,0 +1,208 @@
|
||||
-- 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();
|
||||
Reference in New Issue
Block a user