-- View-once media: per-attachment opt-in. When a recipient opens the -- attachment for the first time we delete the storage object and replace -- it with a tombstone marker (kept_until / viewed_at). The encrypted -- attachment row stays so the bubble can render "Angesehen". alter table public.message_attachments add column if not exists view_once boolean not null default false, add column if not exists viewed_at timestamptz null, add column if not exists viewed_by uuid null references auth.users(id) on delete set null; -- Mark-viewed RPC: server-authoritative so the recipient can't replay the -- decrypted blob across devices. Atomic: only the FIRST viewer wins. The -- function returns the previous `viewed_at` so the client can distinguish -- first-open (got it) from already-viewed (too late, sorry). create or replace function public.mark_attachment_viewed(p_attachment_id uuid) returns jsonb language plpgsql security definer set search_path = public as $$ declare caller uuid := auth.uid(); att public.message_attachments%rowtype; msg public.messages%rowtype; prev timestamptz; begin if caller is null then raise exception 'not authenticated'; end if; select * into att from public.message_attachments where id = p_attachment_id for update; if not found then raise exception 'attachment not found'; end if; if not att.view_once then return jsonb_build_object('view_once', false); end if; select * into msg from public.messages where id = att.message_id; if not found then raise exception 'orphan attachment'; end if; if not public.is_conversation_member(msg.conversation_id) then raise exception 'not a member'; end if; -- Sender opening their own view-once doesn't "burn" it — they sent it. if msg.sender_id = caller then return jsonb_build_object('view_once', true, 'viewed_at', att.viewed_at, 'self', true); end if; if att.viewed_at is not null then return jsonb_build_object('view_once', true, 'viewed_at', att.viewed_at, 'already', true); end if; update public.message_attachments set viewed_at = now(), viewed_by = caller where id = p_attachment_id returning viewed_at into prev; return jsonb_build_object('view_once', true, 'viewed_at', prev); end; $$; revoke execute on function public.mark_attachment_viewed(uuid) from public, anon; grant execute on function public.mark_attachment_viewed(uuid) to authenticated;