232 lines
5.9 KiB
PL/PgSQL
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;
|