refactor: remove obsolete member management API module
Some checks failed
Workflow / ʦ TypeScript (pull_request) Failing after 5m57s
Workflow / ⚫️ Test (pull_request) Has been skipped

This commit is contained in:
T. Zehetbauer
2026-04-03 14:08:31 +02:00
parent 124c6a632a
commit 5c5aaabae5
132 changed files with 10107 additions and 3442 deletions

View File

@@ -0,0 +1,125 @@
-- =====================================================
-- Soft Delete Consistency
--
-- Problem: deleteMember does a soft delete (status='resigned'),
-- but child table FKs use ON DELETE CASCADE. A hard DELETE
-- would silently destroy roles, honors, mandates, transfers
-- with no audit trail.
--
-- Fix: Change CASCADE to RESTRICT on data-preserving tables,
-- add BEFORE DELETE audit trigger, provide safe_delete_member().
-- =====================================================
-- Step 1: Change ON DELETE CASCADE → RESTRICT on tables where
-- child data has independent value and should be preserved.
-- We must drop and recreate the FK constraints.
-- member_roles: board positions have historical value
ALTER TABLE public.member_roles
DROP CONSTRAINT IF EXISTS member_roles_member_id_fkey;
ALTER TABLE public.member_roles
ADD CONSTRAINT member_roles_member_id_fkey
FOREIGN KEY (member_id) REFERENCES public.members(id) ON DELETE RESTRICT;
-- member_honors: awards/medals are permanent records
ALTER TABLE public.member_honors
DROP CONSTRAINT IF EXISTS member_honors_member_id_fkey;
ALTER TABLE public.member_honors
ADD CONSTRAINT member_honors_member_id_fkey
FOREIGN KEY (member_id) REFERENCES public.members(id) ON DELETE RESTRICT;
-- sepa_mandates: financial records must be preserved
ALTER TABLE public.sepa_mandates
DROP CONSTRAINT IF EXISTS sepa_mandates_member_id_fkey;
ALTER TABLE public.sepa_mandates
ADD CONSTRAINT sepa_mandates_member_id_fkey
FOREIGN KEY (member_id) REFERENCES public.members(id) ON DELETE RESTRICT;
-- member_transfers: audit trail must survive
ALTER TABLE public.member_transfers
DROP CONSTRAINT IF EXISTS member_transfers_member_id_fkey;
ALTER TABLE public.member_transfers
ADD CONSTRAINT member_transfers_member_id_fkey
FOREIGN KEY (member_id) REFERENCES public.members(id) ON DELETE RESTRICT;
-- Keep CASCADE on tables where data is tightly coupled:
-- member_department_assignments (junction table, no independent value)
-- member_cards (regeneratable)
-- member_portal_invitations (transient)
-- Step 2: Audit trigger before hard delete — snapshot the full record
CREATE OR REPLACE FUNCTION public.audit_member_before_hard_delete()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
BEGIN
-- If an audit_log table exists, log the deletion
INSERT INTO public.audit_log (
account_id, user_id, table_name, record_id, action, old_data
)
SELECT
OLD.account_id,
COALESCE(
nullif(current_setting('app.current_user_id', true), '')::uuid,
auth.uid()
),
'members',
OLD.id::text,
'delete',
to_jsonb(OLD);
RETURN OLD;
EXCEPTION
WHEN undefined_table THEN
-- audit_log table doesn't exist yet, allow delete to proceed
RETURN OLD;
END;
$$;
CREATE TRIGGER trg_members_audit_before_delete
BEFORE DELETE ON public.members
FOR EACH ROW
EXECUTE FUNCTION public.audit_member_before_hard_delete();
-- Step 3: Safe hard-delete function for super-admin use only
-- Archives all child records first, then performs the delete.
CREATE OR REPLACE FUNCTION public.safe_delete_member(
p_member_id uuid,
p_performed_by uuid DEFAULT NULL
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_member record;
BEGIN
-- Fetch member for validation
SELECT * INTO v_member FROM public.members WHERE id = p_member_id;
IF v_member IS NULL THEN
RAISE EXCEPTION 'Member % not found', p_member_id
USING ERRCODE = 'P0002';
END IF;
-- Set the user ID for the audit trigger
IF p_performed_by IS NOT NULL THEN
PERFORM set_config('app.current_user_id', p_performed_by::text, true);
END IF;
-- Delete child records that now use RESTRICT
DELETE FROM public.member_roles WHERE member_id = p_member_id;
DELETE FROM public.member_honors WHERE member_id = p_member_id;
DELETE FROM public.sepa_mandates WHERE member_id = p_member_id;
-- member_transfers: delete (the BEFORE DELETE trigger on members already snapshots everything)
DELETE FROM public.member_transfers WHERE member_id = p_member_id;
-- Now the hard delete triggers audit_member_before_hard_delete
DELETE FROM public.members WHERE id = p_member_id;
END;
$$;
GRANT EXECUTE ON FUNCTION public.safe_delete_member(uuid, uuid) TO service_role;
-- Intentionally NOT granted to authenticated — super-admin only via admin client