-- Migration 021: delete_account() RPC — Fase 10.5. -- -- Hard-deletes the caller's account. The content they created stays in the -- collectives (FKs were relaxed in migration 019 to SET NULL). -- -- Guard (errcode P0003): if the caller is the only admin of any collective -- AND no eligible member (member or admin, not guest) exists to be -- auto-promoted, the deletion is blocked — otherwise the collective ends -- up with zero admins and nobody can manage it. The user is asked to -- promote someone manually or kick the remaining non-eligible members. -- -- Order of deletes matters: -- 1. DELETE FROM public.users — fires `promote_oldest_member_on_admin_leave` -- (migration 002) BEFORE the cascade removes collective_members. That -- trigger promotes the oldest non-departing member to admin. Then the -- cascade removes the user's memberships. Content rows with -- created_by = v_user fall to NULL. -- 2. DELETE FROM auth.users — cascades sync_conflicts, removes the GoTrue -- identity. We do NOT touch Keycloak; the user can re-register with a -- new UUID (documented limitation, see plan/fase-10 §10.5.4). CREATE OR REPLACE FUNCTION public.delete_account() RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, auth AS $$ DECLARE v_user uuid := auth.uid(); v_blocked boolean; BEGIN IF v_user IS NULL THEN RAISE EXCEPTION 'not authenticated' USING ERRCODE = '28000'; END IF; -- Pre-flight: is the user the sole admin of any collective that has -- members but no other promotable member? SELECT EXISTS ( SELECT 1 FROM public.collective_members cm WHERE cm.user_id = v_user AND cm.role = 'admin' -- More than one member in the collective AND (SELECT count(*) FROM public.collective_members c2 WHERE c2.collective_id = cm.collective_id) > 1 -- And nobody else is admin OR member (only guests + the caller) AND NOT EXISTS ( SELECT 1 FROM public.collective_members c3 WHERE c3.collective_id = cm.collective_id AND c3.user_id <> v_user AND c3.role IN ('admin', 'member') ) ) INTO v_blocked; IF v_blocked THEN RAISE EXCEPTION 'sole admin with non-promotable members' USING ERRCODE = 'P0003'; END IF; -- Step 1: public.users — trigger promotes oldest member if needed, -- then cascade clears collective_members, SET NULL on content FKs. DELETE FROM public.users WHERE id = v_user; -- Step 2: auth.users — sweeps sync_conflicts (CASCADE) + GoTrue row. DELETE FROM auth.users WHERE id = v_user; END; $$; REVOKE ALL ON FUNCTION public.delete_account() FROM PUBLIC; GRANT EXECUTE ON FUNCTION public.delete_account() TO authenticated;