Migration 018 adds public.dissolve_collective(uuid): - admin-only; non-admin (member/guest/non-member) → errcode P0002 - single DELETE from public.collectives; all child tables cascade (collective_members, collective_invitations, shopping_lists → shopping_items, task_lists → tasks, notes, item_frequency, sync_conflicts — verified by audit, no schema change needed) /collective/manage gains a "Danger zone" card (admin-only) with a Dissolve button. The confirmation modal loads live counts of lists, tasks and notes, displays them in the warning, and requires the user to type the collective name exactly before the confirm button is enabled. On success, locals stores drop the collective and the user is sent to /lists (next collective) or /onboarding (none left). pgTAP 011 (8 assertions): RPC exists, member/guest/non-member all rejected with P0002, admin succeeds, collective row gone, child list cascade-deleted, members cascade-deleted. Playwright D-01..D-03 cover the happy path, the type-the-name guard, and member visibility. Auxiliary collective fixture (NOT the seed) per test — the seed collective must survive every run for downstream suites. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
41 lines
1.4 KiB
PL/PgSQL
41 lines
1.4 KiB
PL/PgSQL
-- Migration 018: dissolve_collective(uuid) RPC — Fase 10.3 (CU-H08).
|
|
--
|
|
-- Permanently delete a collective and all its content. Admin-only. Errcode
|
|
-- P0002 so the UI can distinguish "you're not admin" from generic errors.
|
|
--
|
|
-- All content tables that reference public.collectives.collective_id are
|
|
-- already ON DELETE CASCADE (collective_members, collective_invitations,
|
|
-- shopping_lists → shopping_items, task_lists → tasks, notes, item_frequency,
|
|
-- sync_conflicts). Deleting the parent row cleans up the entire subtree.
|
|
-- See plan/fase-10-spec-completion.md §10.3 for the audit + Riesgo 2.
|
|
|
|
CREATE OR REPLACE FUNCTION public.dissolve_collective(p_collective_id uuid)
|
|
RETURNS void
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = public, auth
|
|
AS $$
|
|
DECLARE
|
|
v_user uuid := auth.uid();
|
|
v_role public.member_role;
|
|
BEGIN
|
|
IF v_user IS NULL THEN
|
|
RAISE EXCEPTION 'not authenticated' USING ERRCODE = '28000';
|
|
END IF;
|
|
|
|
SELECT role INTO v_role
|
|
FROM public.collective_members
|
|
WHERE collective_id = p_collective_id AND user_id = v_user;
|
|
|
|
IF v_role IS DISTINCT FROM 'admin' THEN
|
|
RAISE EXCEPTION 'only admins can dissolve a collective'
|
|
USING ERRCODE = 'P0002';
|
|
END IF;
|
|
|
|
DELETE FROM public.collectives WHERE id = p_collective_id;
|
|
END;
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION public.dissolve_collective(uuid) FROM PUBLIC;
|
|
GRANT EXECUTE ON FUNCTION public.dissolve_collective(uuid) TO authenticated;
|