From 745b8cd69d843eadd476bc232ac7de6ac8cef06e Mon Sep 17 00:00:00 2001 From: byGalax Date: Sat, 16 May 2026 17:39:42 +0200 Subject: [PATCH] =?UTF-8?q?feat(db):=20pinned=5Fmessages=20table=20(?= =?UTF-8?q?=E2=89=A45=20per=20conv=20via=20trigger)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20260516000002_pinned_messages.sql | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 supabase/migrations/20260516000002_pinned_messages.sql diff --git a/supabase/migrations/20260516000002_pinned_messages.sql b/supabase/migrations/20260516000002_pinned_messages.sql new file mode 100644 index 0000000..cb9ee5a --- /dev/null +++ b/supabase/migrations/20260516000002_pinned_messages.sql @@ -0,0 +1,61 @@ +-- Pinned messages: each conversation gets up to 5 anchored references to its +-- own messages. Any accepted member can pin/unpin. RLS mirrors conversation +-- membership; the cap is enforced by a BEFORE INSERT trigger because partial +-- unique indexes can't express "at most N rows per group". + +create table if not exists public.pinned_messages ( + conversation_id uuid not null references public.conversations(id) on delete cascade, + message_id uuid not null references public.messages(id) on delete cascade, + pinned_by uuid not null references auth.users(id) on delete set null, + pinned_at timestamptz not null default now(), + primary key (conversation_id, message_id) +); + +create index if not exists pinned_messages_conv_idx + on public.pinned_messages(conversation_id, pinned_at desc); + +alter table public.pinned_messages enable row level security; + +drop policy if exists pinned_messages_select_member on public.pinned_messages; +create policy pinned_messages_select_member + on public.pinned_messages + for select + to authenticated + using (public.is_conversation_member(conversation_id)); + +drop policy if exists pinned_messages_insert_member on public.pinned_messages; +create policy pinned_messages_insert_member + on public.pinned_messages + for insert + to authenticated + with check ( + public.is_conversation_member(conversation_id) + and pinned_by = auth.uid() + ); + +drop policy if exists pinned_messages_delete_member on public.pinned_messages; +create policy pinned_messages_delete_member + on public.pinned_messages + for delete + to authenticated + using (public.is_conversation_member(conversation_id)); + +-- Enforce the per-conversation cap. Trigger-based so cross-row counts work. +create or replace function public.pinned_messages_enforce_cap() +returns trigger +language plpgsql +as $$ +begin + if (select count(*) from public.pinned_messages where conversation_id = new.conversation_id) >= 5 then + raise exception 'pinned_messages_cap_reached: at most 5 pins per conversation'; + end if; + return new; +end; +$$; + +drop trigger if exists pinned_messages_cap on public.pinned_messages; +create trigger pinned_messages_cap + before insert on public.pinned_messages + for each row execute function public.pinned_messages_enforce_cap(); + +alter publication supabase_realtime add table public.pinned_messages;