Files
myeasycms-v2/apps/web/supabase/migrations/20260417000008_audit_timeline_rpcs.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

232 lines
5.9 KiB
PL/PgSQL

-- =====================================================
-- Audit Timeline RPCs for Courses, Events, Bookings
--
-- Paginated, filterable read layer on the audit logs.
-- Mirrors get_member_timeline from 20260416000007.
-- =====================================================
-- -------------------------------------------------------
-- 1. Course timeline RPC
-- -------------------------------------------------------
CREATE OR REPLACE FUNCTION public.get_course_timeline(
p_course_id uuid,
p_page integer DEFAULT 1,
p_page_size integer DEFAULT 50,
p_action_filter text DEFAULT NULL
)
RETURNS TABLE (
id bigint,
action text,
changes jsonb,
metadata jsonb,
user_id uuid,
user_email text,
created_at timestamptz,
total_count bigint
)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_account_id uuid;
v_total bigint;
v_offset integer;
BEGIN
-- Get course's account for access check
SELECT c.account_id INTO v_account_id
FROM public.courses c WHERE c.id = p_course_id;
IF v_account_id IS NULL THEN
RAISE EXCEPTION 'Course not found';
END IF;
IF NOT public.has_role_on_account(v_account_id) THEN
RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501';
END IF;
-- Clamp page size to prevent unbounded queries
p_page_size := LEAST(GREATEST(p_page_size, 1), 200);
v_offset := GREATEST(0, (p_page - 1)) * p_page_size;
-- Get total count
SELECT count(*) INTO v_total
FROM public.course_audit_log cal
WHERE cal.course_id = p_course_id
AND (p_action_filter IS NULL OR cal.action = p_action_filter);
-- Return paginated results with user email
RETURN QUERY
SELECT
cal.id,
cal.action,
cal.changes,
cal.metadata,
cal.user_id,
u.email::text AS user_email,
cal.created_at,
v_total AS total_count
FROM public.course_audit_log cal
LEFT JOIN auth.users u ON u.id = cal.user_id
WHERE cal.course_id = p_course_id
AND (p_action_filter IS NULL OR cal.action = p_action_filter)
ORDER BY cal.created_at DESC
LIMIT p_page_size OFFSET v_offset;
END;
$$;
GRANT EXECUTE ON FUNCTION public.get_course_timeline(uuid, integer, integer, text)
TO authenticated, service_role;
-- -------------------------------------------------------
-- 2. Event timeline RPC
-- -------------------------------------------------------
CREATE OR REPLACE FUNCTION public.get_event_timeline(
p_event_id uuid,
p_page integer DEFAULT 1,
p_page_size integer DEFAULT 50,
p_action_filter text DEFAULT NULL
)
RETURNS TABLE (
id bigint,
action text,
changes jsonb,
metadata jsonb,
user_id uuid,
user_email text,
created_at timestamptz,
total_count bigint
)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_account_id uuid;
v_total bigint;
v_offset integer;
BEGIN
-- Get event's account for access check
SELECT e.account_id INTO v_account_id
FROM public.events e WHERE e.id = p_event_id;
IF v_account_id IS NULL THEN
RAISE EXCEPTION 'Event not found';
END IF;
IF NOT public.has_role_on_account(v_account_id) THEN
RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501';
END IF;
-- Clamp page size to prevent unbounded queries
p_page_size := LEAST(GREATEST(p_page_size, 1), 200);
v_offset := GREATEST(0, (p_page - 1)) * p_page_size;
-- Get total count
SELECT count(*) INTO v_total
FROM public.event_audit_log eal
WHERE eal.event_id = p_event_id
AND (p_action_filter IS NULL OR eal.action = p_action_filter);
-- Return paginated results with user email
RETURN QUERY
SELECT
eal.id,
eal.action,
eal.changes,
eal.metadata,
eal.user_id,
u.email::text AS user_email,
eal.created_at,
v_total AS total_count
FROM public.event_audit_log eal
LEFT JOIN auth.users u ON u.id = eal.user_id
WHERE eal.event_id = p_event_id
AND (p_action_filter IS NULL OR eal.action = p_action_filter)
ORDER BY eal.created_at DESC
LIMIT p_page_size OFFSET v_offset;
END;
$$;
GRANT EXECUTE ON FUNCTION public.get_event_timeline(uuid, integer, integer, text)
TO authenticated, service_role;
-- -------------------------------------------------------
-- 3. Booking timeline RPC
-- -------------------------------------------------------
CREATE OR REPLACE FUNCTION public.get_booking_timeline(
p_booking_id uuid,
p_page integer DEFAULT 1,
p_page_size integer DEFAULT 50,
p_action_filter text DEFAULT NULL
)
RETURNS TABLE (
id bigint,
action text,
changes jsonb,
metadata jsonb,
user_id uuid,
user_email text,
created_at timestamptz,
total_count bigint
)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = ''
AS $$
DECLARE
v_account_id uuid;
v_total bigint;
v_offset integer;
BEGIN
-- Get booking's account for access check
SELECT b.account_id INTO v_account_id
FROM public.bookings b WHERE b.id = p_booking_id;
IF v_account_id IS NULL THEN
RAISE EXCEPTION 'Booking not found';
END IF;
IF NOT public.has_role_on_account(v_account_id) THEN
RAISE EXCEPTION 'Access denied' USING ERRCODE = '42501';
END IF;
-- Clamp page size to prevent unbounded queries
p_page_size := LEAST(GREATEST(p_page_size, 1), 200);
v_offset := GREATEST(0, (p_page - 1)) * p_page_size;
-- Get total count
SELECT count(*) INTO v_total
FROM public.booking_audit_log bal
WHERE bal.booking_id = p_booking_id
AND (p_action_filter IS NULL OR bal.action = p_action_filter);
-- Return paginated results with user email
RETURN QUERY
SELECT
bal.id,
bal.action,
bal.changes,
bal.metadata,
bal.user_id,
u.email::text AS user_email,
bal.created_at,
v_total AS total_count
FROM public.booking_audit_log bal
LEFT JOIN auth.users u ON u.id = bal.user_id
WHERE bal.booking_id = p_booking_id
AND (p_action_filter IS NULL OR bal.action = p_action_filter)
ORDER BY bal.created_at DESC
LIMIT p_page_size OFFSET v_offset;
END;
$$;
GRANT EXECUTE ON FUNCTION public.get_booking_timeline(uuid, integer, integer, text)
TO authenticated, service_role;