feat: add cross-organization member search and template cloning functionality

This commit is contained in:
T. Zehetbauer
2026-04-01 10:15:35 +02:00
parent d3db316a68
commit fd8c2cc32a
36 changed files with 9025 additions and 94 deletions

View File

@@ -54,16 +54,16 @@ BEGIN
-- Walk up from the proposed parent; if we find NEW.id, it's a cycle
IF EXISTS (
WITH RECURSIVE ancestors AS (
SELECT id, parent_account_id
FROM public.accounts
WHERE id = NEW.parent_account_id
SELECT acc.id, acc.parent_account_id, ARRAY[acc.id] AS path
FROM public.accounts acc
WHERE acc.id = NEW.parent_account_id
UNION ALL
SELECT a.id, a.parent_account_id
SELECT a.id, a.parent_account_id, anc.path || a.id
FROM public.accounts a
JOIN ancestors anc ON a.id = anc.parent_account_id
WHERE NOT a.id = ANY(anc.path) -- cycle guard
)
CYCLE id SET is_cycle USING path
SELECT 1 FROM ancestors WHERE id = NEW.id AND NOT is_cycle
SELECT 1 FROM ancestors WHERE id = NEW.id
) THEN
RAISE EXCEPTION 'Setting parent_account_id would create a hierarchy cycle';
END IF;
@@ -73,7 +73,9 @@ BEGIN
END;
$$;
CREATE OR REPLACE TRIGGER prevent_account_hierarchy_cycle
DROP TRIGGER IF EXISTS prevent_account_hierarchy_cycle ON public.accounts;
CREATE TRIGGER prevent_account_hierarchy_cycle
BEFORE INSERT OR UPDATE OF parent_account_id
ON public.accounts
FOR EACH ROW
@@ -82,7 +84,8 @@ CREATE OR REPLACE TRIGGER prevent_account_hierarchy_cycle
-- -------------------------------------------------------
-- Helper: get all descendant account IDs (recursive, cycle-safe)
-- Restricted to service_role — called via RLS helper functions
-- Uses path array for cycle detection (works on Postgres 14+)
-- Restricted to service_role — called via RLS SECURITY DEFINER functions
-- -------------------------------------------------------
CREATE OR REPLACE FUNCTION public.get_account_descendants(root_id uuid)
RETURNS SETOF uuid
@@ -90,15 +93,15 @@ LANGUAGE sql STABLE
SET search_path = ''
AS $$
WITH RECURSIVE tree AS (
SELECT id, parent_account_id
FROM public.accounts WHERE id = root_id
SELECT acc.id, ARRAY[acc.id] AS path
FROM public.accounts acc WHERE acc.id = root_id
UNION ALL
SELECT a.id, a.parent_account_id
SELECT a.id, t.path || a.id
FROM public.accounts a
JOIN tree t ON a.parent_account_id = t.id
WHERE NOT a.id = ANY(t.path)
)
CYCLE id SET is_cycle USING path
SELECT id FROM tree WHERE NOT is_cycle;
SELECT tree.id FROM tree;
$$;
GRANT EXECUTE ON FUNCTION public.get_account_descendants(uuid)
@@ -106,7 +109,7 @@ GRANT EXECUTE ON FUNCTION public.get_account_descendants(uuid)
-- -------------------------------------------------------
-- Helper: get all ancestor account IDs (walk up, cycle-safe)
-- Restricted to service_role — called via RLS helper functions
-- Restricted to service_role
-- -------------------------------------------------------
CREATE OR REPLACE FUNCTION public.get_account_ancestors(child_id uuid)
RETURNS SETOF uuid
@@ -114,15 +117,15 @@ LANGUAGE sql STABLE
SET search_path = ''
AS $$
WITH RECURSIVE tree AS (
SELECT id, parent_account_id
FROM public.accounts WHERE id = child_id
SELECT acc.id, acc.parent_account_id, ARRAY[acc.id] AS path
FROM public.accounts acc WHERE acc.id = child_id
UNION ALL
SELECT a.id, a.parent_account_id
SELECT a.id, a.parent_account_id, t.path || a.id
FROM public.accounts a
JOIN tree t ON a.id = t.parent_account_id
WHERE NOT a.id = ANY(t.path)
)
CYCLE id SET is_cycle USING path
SELECT id FROM tree WHERE NOT is_cycle;
SELECT tree.id FROM tree;
$$;
GRANT EXECUTE ON FUNCTION public.get_account_ancestors(uuid)
@@ -138,16 +141,16 @@ LANGUAGE sql STABLE
SET search_path = ''
AS $$
WITH RECURSIVE tree AS (
SELECT id, parent_account_id, 0 AS depth
FROM public.accounts
WHERE id = account_id
SELECT acc.id, acc.parent_account_id, 0 AS depth, ARRAY[acc.id] AS path
FROM public.accounts acc
WHERE acc.id = get_account_depth.account_id
UNION ALL
SELECT a.id, a.parent_account_id, t.depth + 1
SELECT a.id, a.parent_account_id, t.depth + 1, t.path || a.id
FROM public.accounts a
JOIN tree t ON a.id = t.parent_account_id
WHERE NOT a.id = ANY(t.path)
)
CYCLE id SET is_cycle USING path
SELECT MAX(depth) FROM tree WHERE NOT is_cycle;
SELECT MAX(depth) FROM tree;
$$;
GRANT EXECUTE ON FUNCTION public.get_account_depth(uuid)