-- 1) Test-Daten-Wipe: alle conversation/message Daten weg, User+Devices+ -- Friendships+Invites bleiben erhalten so dass bestehende Logins weiter -- funktionieren. Cascade räumt mitlaufende rows (envelopes, attachments, -- reactions, reads, conversation_keys, conversation_members). truncate public.message_reactions, public.message_reads, public.message_attachments, public.messages, public.conversation_keys, public.conversation_members, public.conversations restart identity cascade; -- 2) RPC: share conv-key bundles to multiple recipients atomically. -- Runs as SECURITY DEFINER so RLS doesn't reject individual rows. The -- function itself enforces the same invariants as the policy. This avoids -- the per-row 403 console spam we got with direct inserts. create or replace function public.share_conv_keys( p_conv_id uuid, p_sender_device_id uuid, p_key_version int, p_bundles jsonb ) returns int language plpgsql security definer set search_path = public as $$ declare caller uuid := auth.uid(); bundle jsonb; recipient_user_id uuid; inserted int := 0; recipient_did uuid; enc_key_hex text; nonce_hex text; begin if caller is null then raise exception 'not authenticated'; end if; -- Caller must be an accepted member of the conversation. if not exists ( select 1 from public.conversation_members where conversation_id = p_conv_id and user_id = caller and accepted = true ) then raise exception 'caller is not an accepted member of %', p_conv_id; end if; -- Sender device must belong to the caller. if not exists ( select 1 from public.devices where id = p_sender_device_id and user_id = caller ) then raise exception 'sender_device % not owned by caller', p_sender_device_id; end if; -- Iterate bundles and only insert ones whose recipient is a current -- accepted member. Silently skip otherwise (no error → no 403 noise). for bundle in select * from jsonb_array_elements(p_bundles) loop recipient_did := (bundle->>'recipient_device_id')::uuid; enc_key_hex := bundle->>'encrypted_key'; nonce_hex := bundle->>'nonce'; select user_id into recipient_user_id from public.devices where id = recipient_did; if recipient_user_id is null then continue; end if; if not exists ( select 1 from public.conversation_members where conversation_id = p_conv_id and user_id = recipient_user_id and accepted = true ) then continue; end if; insert into public.conversation_keys (conversation_id, recipient_device_id, key_version, sender_device_id, encrypted_key, nonce) values (p_conv_id, recipient_did, p_key_version, p_sender_device_id, decode(enc_key_hex, 'hex'), decode(nonce_hex, 'hex')) on conflict (conversation_id, recipient_device_id, key_version) do nothing; if found then inserted := inserted + 1; end if; end loop; return inserted; end; $$; revoke execute on function public.share_conv_keys(uuid, uuid, int, jsonb) from public, anon; grant execute on function public.share_conv_keys(uuid, uuid, int, jsonb) to authenticated;