feat: add cross-organization member search and template cloning functionality
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user