-- Per-mention rows so realtime can notify mentioned users without leaking -- the rest of a conversation. Each mention is the pair (message, user_id). create table if not exists public.message_mentions ( message_id uuid not null references public.messages(id) on delete cascade, mentioned_user_id uuid not null references auth.users(id) on delete cascade, conversation_id uuid not null references public.conversations(id) on delete cascade, created_at timestamptz not null default now(), primary key (message_id, mentioned_user_id) ); create index if not exists message_mentions_user_idx on public.message_mentions(mentioned_user_id, created_at desc); alter table public.message_mentions enable row level security; -- The mentioned user OR the message author can SELECT. RLS uses messages -- to confirm the caller is the author. drop policy if exists message_mentions_select on public.message_mentions; create policy message_mentions_select on public.message_mentions for select to authenticated using ( mentioned_user_id = auth.uid() or exists ( select 1 from public.messages m where m.id = message_id and m.sender_id = auth.uid() ) ); -- INSERT allowed when the caller is the author of the referenced message -- AND the mentioned user is also a member of the same conversation. drop policy if exists message_mentions_insert_author on public.message_mentions; create policy message_mentions_insert_author on public.message_mentions for insert to authenticated with check ( exists ( select 1 from public.messages m where m.id = message_id and m.sender_id = auth.uid() and m.conversation_id = message_mentions.conversation_id ) and public.is_conversation_member(conversation_id) and exists ( select 1 from public.conversation_members cm where cm.conversation_id = message_mentions.conversation_id and cm.user_id = mentioned_user_id and cm.accepted ) ); alter publication supabase_realtime add table public.message_mentions;