diff --git a/supabase/migrations/20260516000006_whiteboards.sql b/supabase/migrations/20260516000006_whiteboards.sql new file mode 100644 index 0000000..f0d6643 --- /dev/null +++ b/supabase/migrations/20260516000006_whiteboards.sql @@ -0,0 +1,122 @@ +-- Phase 4B: per-conversation whiteboards. +-- +-- conversation_whiteboards: one row per whiteboard. Created by a member; the +-- bubble that appears in the chat message timeline is a normal `messages` +-- row whose plaintext payload is `{v:1, type:'whiteboard', whiteboard_id:}`. +-- +-- whiteboard_strokes: append-only stream of drawing operations. Each row is +-- one user gesture (pen-down → pen-up); stroke_json carries the tool/color/ +-- width/points. Realtime subscribers replay rows in created_at order. + +create table if not exists public.conversation_whiteboards ( + id uuid primary key default gen_random_uuid(), + conversation_id uuid not null references public.conversations(id) on delete cascade, + owner_user_id uuid not null references auth.users(id) on delete cascade, + created_at timestamptz not null default now() +); + +create index if not exists conversation_whiteboards_conv_idx + on public.conversation_whiteboards(conversation_id, created_at desc); + +create table if not exists public.whiteboard_strokes ( + id uuid primary key default gen_random_uuid(), + whiteboard_id uuid not null references public.conversation_whiteboards(id) on delete cascade, + author_user_id uuid not null references auth.users(id) on delete cascade, + stroke_json jsonb not null, + created_at timestamptz not null default now() +); + +create index if not exists whiteboard_strokes_board_idx + on public.whiteboard_strokes(whiteboard_id, created_at asc); + +alter table public.conversation_whiteboards enable row level security; +alter table public.whiteboard_strokes enable row level security; + +drop policy if exists conversation_whiteboards_select on public.conversation_whiteboards; +drop policy if exists conversation_whiteboards_insert on public.conversation_whiteboards; +drop policy if exists conversation_whiteboards_delete on public.conversation_whiteboards; +drop policy if exists whiteboard_strokes_select on public.whiteboard_strokes; +drop policy if exists whiteboard_strokes_insert on public.whiteboard_strokes; +drop policy if exists whiteboard_strokes_delete on public.whiteboard_strokes; + +create policy conversation_whiteboards_select + on public.conversation_whiteboards + for select + using (public.is_conversation_member(conversation_id)); + +create policy conversation_whiteboards_insert + on public.conversation_whiteboards + for insert + with check ( + public.is_conversation_member(conversation_id) + and owner_user_id = auth.uid() + ); + +create policy conversation_whiteboards_delete + on public.conversation_whiteboards + for delete + using (owner_user_id = auth.uid()); + +create policy whiteboard_strokes_select + on public.whiteboard_strokes + for select + using ( + exists ( + select 1 + from public.conversation_whiteboards w + where w.id = whiteboard_strokes.whiteboard_id + and public.is_conversation_member(w.conversation_id) + ) + ); + +create policy whiteboard_strokes_insert + on public.whiteboard_strokes + for insert + with check ( + author_user_id = auth.uid() + and exists ( + select 1 + from public.conversation_whiteboards w + where w.id = whiteboard_id + and public.is_conversation_member(w.conversation_id) + ) + ); + +create policy whiteboard_strokes_delete + on public.whiteboard_strokes + for delete + using ( + exists ( + select 1 + from public.conversation_whiteboards w + where w.id = whiteboard_strokes.whiteboard_id + and public.is_conversation_member(w.conversation_id) + ) + ); + +alter table public.conversation_whiteboards replica identity full; +alter table public.whiteboard_strokes replica identity full; + +do $$ +begin + if not exists ( + select 1 + from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'conversation_whiteboards' + ) then + execute 'alter publication supabase_realtime add table public.conversation_whiteboards'; + end if; + + if not exists ( + select 1 + from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'whiteboard_strokes' + ) then + execute 'alter publication supabase_realtime add table public.whiteboard_strokes'; + end if; +end +$$;