From ad239ec549f912936a1878b8147e4736c279d63a Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 21:16:38 +0200 Subject: [PATCH] feat(P5A.T1): conversation_watch_sessions table with RLS + realtime --- .../20260516000008_watch_together.sql | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 supabase/migrations/20260516000008_watch_together.sql diff --git a/supabase/migrations/20260516000008_watch_together.sql b/supabase/migrations/20260516000008_watch_together.sql new file mode 100644 index 0000000..796abcb --- /dev/null +++ b/supabase/migrations/20260516000008_watch_together.sql @@ -0,0 +1,63 @@ +-- Phase 5A: per-conversation synchronized YouTube playback. +-- +-- conversation_watch_sessions: one row per Watch-Together session. The bubble +-- in the chat is a normal `messages` row whose plaintext payload is +-- `{v:1, type:'watch_together', session_id:}`. The owner's player drives +-- current_state (jsonb {playing, position_seconds, updated_at_ms}); other +-- joiners reconcile via realtime postgres_changes UPDATE when local drift +-- exceeds 2 seconds. + +create table if not exists public.conversation_watch_sessions ( + 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, + video_id text not null, + started_at timestamptz not null default now(), + ended_at timestamptz null, + current_state jsonb not null default '{"playing":false,"position_seconds":0,"updated_at_ms":0}'::jsonb, + constraint conversation_watch_sessions_video_id_len check (length(video_id) between 1 and 64) +); + +create index if not exists conversation_watch_sessions_conv_idx + on public.conversation_watch_sessions(conversation_id, started_at desc); + +alter table public.conversation_watch_sessions enable row level security; + +drop policy if exists conversation_watch_sessions_select on public.conversation_watch_sessions; +drop policy if exists conversation_watch_sessions_insert on public.conversation_watch_sessions; +drop policy if exists conversation_watch_sessions_update on public.conversation_watch_sessions; + +create policy conversation_watch_sessions_select + on public.conversation_watch_sessions + for select + using (public.is_conversation_member(conversation_id)); + +create policy conversation_watch_sessions_insert + on public.conversation_watch_sessions + for insert + with check ( + public.is_conversation_member(conversation_id) + and owner_user_id = auth.uid() + ); + +create policy conversation_watch_sessions_update + on public.conversation_watch_sessions + for update + using (owner_user_id = auth.uid()) + with check (owner_user_id = auth.uid()); + +alter table public.conversation_watch_sessions replica identity full; + +do $$ +begin + if not exists ( + select 1 + from pg_publication_tables + where pubname = 'supabase_realtime' + and schemaname = 'public' + and tablename = 'conversation_watch_sessions' + ) then + execute 'alter publication supabase_realtime add table public.conversation_watch_sessions'; + end if; +end +$$;