Files
myeasycms-v2/apps/web/supabase/migrations/20260416000001_atomic_application_workflow.sql
T. Zehetbauer 5c5aaabae5
Some checks failed
Workflow / ʦ TypeScript (pull_request) Failing after 5m57s
Workflow / ⚫️ Test (pull_request) Has been skipped
refactor: remove obsolete member management API module
2026-04-03 14:08:31 +02:00

155 lines
4.0 KiB
PL/PgSQL

-- =====================================================
-- Atomic Application Workflow
-- Replaces multi-query approve/reject in api.ts with
-- single transactional PG functions.
-- =====================================================
-- approve_application: atomically creates a member from an application
CREATE OR REPLACE FUNCTION public.approve_application(
p_application_id uuid,
p_user_id uuid
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_app record;
v_member_id uuid;
v_member_number text;
BEGIN
-- 1. Fetch and lock the application
SELECT * INTO v_app
FROM public.membership_applications
WHERE id = p_application_id
FOR UPDATE;
IF v_app IS NULL THEN
RAISE EXCEPTION 'Application % not found', p_application_id
USING ERRCODE = 'P0002';
END IF;
-- Authorization: caller must have write permission on this account
IF NOT public.has_permission(auth.uid(), v_app.account_id, 'members.write'::public.app_permissions) THEN
RAISE EXCEPTION 'Access denied to account %', v_app.account_id
USING ERRCODE = '42501';
END IF;
IF v_app.status NOT IN ('submitted', 'review') THEN
RAISE EXCEPTION 'Application is not in a reviewable state (current: %)', v_app.status
USING ERRCODE = 'P0001';
END IF;
-- 2. Generate next member number
SELECT LPAD(
(COALESCE(
MAX(CASE WHEN member_number ~ '^\d+$' THEN member_number::integer ELSE 0 END),
0
) + 1)::text,
4, '0'
) INTO v_member_number
FROM public.members
WHERE account_id = v_app.account_id;
-- 3. Create the member
INSERT INTO public.members (
account_id,
member_number,
first_name,
last_name,
email,
phone,
street,
postal_code,
city,
date_of_birth,
status,
entry_date,
created_by,
updated_by
) VALUES (
v_app.account_id,
v_member_number,
v_app.first_name,
v_app.last_name,
v_app.email,
v_app.phone,
v_app.street,
v_app.postal_code,
v_app.city,
v_app.date_of_birth,
'active'::public.membership_status,
current_date,
auth.uid(),
auth.uid()
)
RETURNING id INTO v_member_id;
-- 4. Mark application as approved
UPDATE public.membership_applications
SET
status = 'approved'::public.application_status,
reviewed_by = auth.uid(),
reviewed_at = now(),
member_id = v_member_id,
updated_at = now()
WHERE id = p_application_id;
RETURN v_member_id;
END;
$$;
GRANT EXECUTE ON FUNCTION public.approve_application(uuid, uuid) TO authenticated;
GRANT EXECUTE ON FUNCTION public.approve_application(uuid, uuid) TO service_role;
-- reject_application: atomically rejects an application with notes
CREATE OR REPLACE FUNCTION public.reject_application(
p_application_id uuid,
p_user_id uuid,
p_review_notes text DEFAULT NULL
)
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_app record;
BEGIN
-- Fetch and lock the application
SELECT * INTO v_app
FROM public.membership_applications
WHERE id = p_application_id
FOR UPDATE;
IF v_app IS NULL THEN
RAISE EXCEPTION 'Application % not found', p_application_id
USING ERRCODE = 'P0002';
END IF;
-- Authorization: caller must have write permission on this account
IF NOT public.has_permission(auth.uid(), v_app.account_id, 'members.write'::public.app_permissions) THEN
RAISE EXCEPTION 'Access denied to account %', v_app.account_id
USING ERRCODE = '42501';
END IF;
IF v_app.status NOT IN ('submitted', 'review') THEN
RAISE EXCEPTION 'Application is not in a reviewable state (current: %)', v_app.status
USING ERRCODE = 'P0001';
END IF;
UPDATE public.membership_applications
SET
status = 'rejected'::public.application_status,
reviewed_by = auth.uid(),
reviewed_at = now(),
review_notes = p_review_notes,
updated_at = now()
WHERE id = p_application_id;
END;
$$;
GRANT EXECUTE ON FUNCTION public.reject_application(uuid, uuid, text) TO authenticated;
GRANT EXECUTE ON FUNCTION public.reject_application(uuid, uuid, text) TO service_role;