497 lines
19 KiB
PL/PgSQL
497 lines
19 KiB
PL/PgSQL
-- =====================================================
|
|
-- Audit Logging for Courses, Events, Bookings
|
|
--
|
|
-- Full change history for compliance: who changed what
|
|
-- field, old value -> new value, when. Mirrors the
|
|
-- member_audit_log pattern from 20260416000007.
|
|
-- =====================================================
|
|
|
|
-- -------------------------------------------------------
|
|
-- A) Add created_by / updated_by to main tables
|
|
-- -------------------------------------------------------
|
|
|
|
ALTER TABLE public.courses
|
|
ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE public.events
|
|
ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;
|
|
|
|
ALTER TABLE public.bookings
|
|
ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS updated_by uuid REFERENCES auth.users(id) ON DELETE SET NULL;
|
|
|
|
-- -------------------------------------------------------
|
|
-- B) Audit log tables
|
|
-- -------------------------------------------------------
|
|
|
|
-- B.1 Course audit log
|
|
CREATE TABLE IF NOT EXISTS public.course_audit_log (
|
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
course_id uuid NOT NULL REFERENCES public.courses(id) ON DELETE CASCADE,
|
|
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
action text NOT NULL CHECK (action IN (
|
|
'created', 'updated', 'status_changed', 'cancelled',
|
|
'participant_enrolled', 'participant_cancelled',
|
|
'participant_waitlisted', 'participant_promoted',
|
|
'session_created', 'session_cancelled',
|
|
'attendance_marked', 'instructor_changed', 'location_changed'
|
|
)),
|
|
changes jsonb NOT NULL DEFAULT '{}',
|
|
metadata jsonb NOT NULL DEFAULT '{}',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE public.course_audit_log IS
|
|
'Immutable audit trail for all course lifecycle events';
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_course_audit_course
|
|
ON public.course_audit_log(course_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_course_audit_account
|
|
ON public.course_audit_log(account_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_course_audit_action
|
|
ON public.course_audit_log(account_id, action);
|
|
|
|
ALTER TABLE public.course_audit_log ENABLE ROW LEVEL SECURITY;
|
|
REVOKE ALL ON public.course_audit_log FROM authenticated, service_role;
|
|
GRANT SELECT ON public.course_audit_log TO authenticated;
|
|
GRANT INSERT, SELECT ON public.course_audit_log TO service_role;
|
|
|
|
CREATE POLICY course_audit_log_select
|
|
ON public.course_audit_log FOR SELECT TO authenticated
|
|
USING (public.has_role_on_account(account_id));
|
|
|
|
-- B.2 Event audit log
|
|
CREATE TABLE IF NOT EXISTS public.event_audit_log (
|
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
event_id uuid NOT NULL REFERENCES public.events(id) ON DELETE CASCADE,
|
|
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
action text NOT NULL CHECK (action IN (
|
|
'created', 'updated', 'status_changed', 'cancelled',
|
|
'registration_confirmed', 'registration_waitlisted',
|
|
'registration_cancelled', 'registration_promoted'
|
|
)),
|
|
changes jsonb NOT NULL DEFAULT '{}',
|
|
metadata jsonb NOT NULL DEFAULT '{}',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE public.event_audit_log IS
|
|
'Immutable audit trail for all event lifecycle events';
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_event_audit_event
|
|
ON public.event_audit_log(event_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_event_audit_account
|
|
ON public.event_audit_log(account_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_event_audit_action
|
|
ON public.event_audit_log(account_id, action);
|
|
|
|
ALTER TABLE public.event_audit_log ENABLE ROW LEVEL SECURITY;
|
|
REVOKE ALL ON public.event_audit_log FROM authenticated, service_role;
|
|
GRANT SELECT ON public.event_audit_log TO authenticated;
|
|
GRANT INSERT, SELECT ON public.event_audit_log TO service_role;
|
|
|
|
CREATE POLICY event_audit_log_select
|
|
ON public.event_audit_log FOR SELECT TO authenticated
|
|
USING (public.has_role_on_account(account_id));
|
|
|
|
-- B.3 Booking audit log
|
|
CREATE TABLE IF NOT EXISTS public.booking_audit_log (
|
|
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
|
booking_id uuid NOT NULL REFERENCES public.bookings(id) ON DELETE CASCADE,
|
|
account_id uuid NOT NULL REFERENCES public.accounts(id) ON DELETE CASCADE,
|
|
user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL,
|
|
action text NOT NULL CHECK (action IN (
|
|
'created', 'updated', 'status_changed',
|
|
'checked_in', 'checked_out', 'cancelled',
|
|
'no_show', 'price_changed'
|
|
)),
|
|
changes jsonb NOT NULL DEFAULT '{}',
|
|
metadata jsonb NOT NULL DEFAULT '{}',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE public.booking_audit_log IS
|
|
'Immutable audit trail for all booking lifecycle events';
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_booking_audit_booking
|
|
ON public.booking_audit_log(booking_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_booking_audit_account
|
|
ON public.booking_audit_log(account_id, created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS ix_booking_audit_action
|
|
ON public.booking_audit_log(account_id, action);
|
|
|
|
ALTER TABLE public.booking_audit_log ENABLE ROW LEVEL SECURITY;
|
|
REVOKE ALL ON public.booking_audit_log FROM authenticated, service_role;
|
|
GRANT SELECT ON public.booking_audit_log TO authenticated;
|
|
GRANT INSERT, SELECT ON public.booking_audit_log TO service_role;
|
|
|
|
CREATE POLICY booking_audit_log_select
|
|
ON public.booking_audit_log FOR SELECT TO authenticated
|
|
USING (public.has_role_on_account(account_id));
|
|
|
|
-- -------------------------------------------------------
|
|
-- C) Auto-audit triggers for UPDATE
|
|
-- -------------------------------------------------------
|
|
|
|
-- C.1 Courses UPDATE trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_course_audit_on_update()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_changes jsonb := '{}'::jsonb;
|
|
v_action text := 'updated';
|
|
v_user_id uuid;
|
|
BEGIN
|
|
-- Build changes diff (field by field)
|
|
IF OLD.name IS DISTINCT FROM NEW.name THEN
|
|
v_changes := v_changes || jsonb_build_object('name', jsonb_build_object('old', OLD.name, 'new', NEW.name));
|
|
END IF;
|
|
IF OLD.description IS DISTINCT FROM NEW.description THEN
|
|
v_changes := v_changes || jsonb_build_object('description', jsonb_build_object('old', OLD.description, 'new', NEW.description));
|
|
END IF;
|
|
IF OLD.course_number IS DISTINCT FROM NEW.course_number THEN
|
|
v_changes := v_changes || jsonb_build_object('course_number', jsonb_build_object('old', OLD.course_number, 'new', NEW.course_number));
|
|
END IF;
|
|
IF OLD.category_id IS DISTINCT FROM NEW.category_id THEN
|
|
v_changes := v_changes || jsonb_build_object('category_id', jsonb_build_object('old', OLD.category_id, 'new', NEW.category_id));
|
|
END IF;
|
|
IF OLD.instructor_id IS DISTINCT FROM NEW.instructor_id THEN
|
|
v_changes := v_changes || jsonb_build_object('instructor_id', jsonb_build_object('old', OLD.instructor_id, 'new', NEW.instructor_id));
|
|
END IF;
|
|
IF OLD.location_id IS DISTINCT FROM NEW.location_id THEN
|
|
v_changes := v_changes || jsonb_build_object('location_id', jsonb_build_object('old', OLD.location_id, 'new', NEW.location_id));
|
|
END IF;
|
|
IF OLD.start_date IS DISTINCT FROM NEW.start_date THEN
|
|
v_changes := v_changes || jsonb_build_object('start_date', jsonb_build_object('old', OLD.start_date, 'new', NEW.start_date));
|
|
END IF;
|
|
IF OLD.end_date IS DISTINCT FROM NEW.end_date THEN
|
|
v_changes := v_changes || jsonb_build_object('end_date', jsonb_build_object('old', OLD.end_date, 'new', NEW.end_date));
|
|
END IF;
|
|
IF OLD.fee IS DISTINCT FROM NEW.fee THEN
|
|
v_changes := v_changes || jsonb_build_object('fee', jsonb_build_object('old', OLD.fee, 'new', NEW.fee));
|
|
END IF;
|
|
IF OLD.reduced_fee IS DISTINCT FROM NEW.reduced_fee THEN
|
|
v_changes := v_changes || jsonb_build_object('reduced_fee', jsonb_build_object('old', OLD.reduced_fee, 'new', NEW.reduced_fee));
|
|
END IF;
|
|
IF OLD.capacity IS DISTINCT FROM NEW.capacity THEN
|
|
v_changes := v_changes || jsonb_build_object('capacity', jsonb_build_object('old', OLD.capacity, 'new', NEW.capacity));
|
|
END IF;
|
|
IF OLD.min_participants IS DISTINCT FROM NEW.min_participants THEN
|
|
v_changes := v_changes || jsonb_build_object('min_participants', jsonb_build_object('old', OLD.min_participants, 'new', NEW.min_participants));
|
|
END IF;
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
|
|
END IF;
|
|
IF OLD.registration_deadline IS DISTINCT FROM NEW.registration_deadline THEN
|
|
v_changes := v_changes || jsonb_build_object('registration_deadline', jsonb_build_object('old', OLD.registration_deadline, 'new', NEW.registration_deadline));
|
|
END IF;
|
|
IF OLD.notes IS DISTINCT FROM NEW.notes THEN
|
|
v_changes := v_changes || jsonb_build_object('notes', jsonb_build_object('old', OLD.notes, 'new', NEW.notes));
|
|
END IF;
|
|
|
|
-- Skip if nothing actually changed
|
|
IF v_changes = '{}'::jsonb THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
-- Classify the action
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_action := 'status_changed';
|
|
END IF;
|
|
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
auth.uid()
|
|
);
|
|
|
|
INSERT INTO public.course_audit_log (course_id, account_id, user_id, action, changes)
|
|
VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);
|
|
|
|
RETURN NULL;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_courses_audit_on_update
|
|
AFTER UPDATE ON public.courses
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_course_audit_on_update();
|
|
|
|
-- C.2 Events UPDATE trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_event_audit_on_update()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_changes jsonb := '{}'::jsonb;
|
|
v_action text := 'updated';
|
|
v_user_id uuid;
|
|
BEGIN
|
|
-- Build changes diff (field by field)
|
|
IF OLD.name IS DISTINCT FROM NEW.name THEN
|
|
v_changes := v_changes || jsonb_build_object('name', jsonb_build_object('old', OLD.name, 'new', NEW.name));
|
|
END IF;
|
|
IF OLD.description IS DISTINCT FROM NEW.description THEN
|
|
v_changes := v_changes || jsonb_build_object('description', jsonb_build_object('old', OLD.description, 'new', NEW.description));
|
|
END IF;
|
|
IF OLD.event_date IS DISTINCT FROM NEW.event_date THEN
|
|
v_changes := v_changes || jsonb_build_object('event_date', jsonb_build_object('old', OLD.event_date, 'new', NEW.event_date));
|
|
END IF;
|
|
IF OLD.event_time IS DISTINCT FROM NEW.event_time THEN
|
|
v_changes := v_changes || jsonb_build_object('event_time', jsonb_build_object('old', OLD.event_time, 'new', NEW.event_time));
|
|
END IF;
|
|
IF OLD.end_date IS DISTINCT FROM NEW.end_date THEN
|
|
v_changes := v_changes || jsonb_build_object('end_date', jsonb_build_object('old', OLD.end_date, 'new', NEW.end_date));
|
|
END IF;
|
|
IF OLD.location IS DISTINCT FROM NEW.location THEN
|
|
v_changes := v_changes || jsonb_build_object('location', jsonb_build_object('old', OLD.location, 'new', NEW.location));
|
|
END IF;
|
|
IF OLD.capacity IS DISTINCT FROM NEW.capacity THEN
|
|
v_changes := v_changes || jsonb_build_object('capacity', jsonb_build_object('old', OLD.capacity, 'new', NEW.capacity));
|
|
END IF;
|
|
IF OLD.min_age IS DISTINCT FROM NEW.min_age THEN
|
|
v_changes := v_changes || jsonb_build_object('min_age', jsonb_build_object('old', OLD.min_age, 'new', NEW.min_age));
|
|
END IF;
|
|
IF OLD.max_age IS DISTINCT FROM NEW.max_age THEN
|
|
v_changes := v_changes || jsonb_build_object('max_age', jsonb_build_object('old', OLD.max_age, 'new', NEW.max_age));
|
|
END IF;
|
|
IF OLD.fee IS DISTINCT FROM NEW.fee THEN
|
|
v_changes := v_changes || jsonb_build_object('fee', jsonb_build_object('old', OLD.fee, 'new', NEW.fee));
|
|
END IF;
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
|
|
END IF;
|
|
IF OLD.registration_deadline IS DISTINCT FROM NEW.registration_deadline THEN
|
|
v_changes := v_changes || jsonb_build_object('registration_deadline', jsonb_build_object('old', OLD.registration_deadline, 'new', NEW.registration_deadline));
|
|
END IF;
|
|
IF OLD.contact_name IS DISTINCT FROM NEW.contact_name THEN
|
|
v_changes := v_changes || jsonb_build_object('contact_name', jsonb_build_object('old', OLD.contact_name, 'new', NEW.contact_name));
|
|
END IF;
|
|
IF OLD.contact_email IS DISTINCT FROM NEW.contact_email THEN
|
|
v_changes := v_changes || jsonb_build_object('contact_email', jsonb_build_object('old', OLD.contact_email, 'new', NEW.contact_email));
|
|
END IF;
|
|
IF OLD.contact_phone IS DISTINCT FROM NEW.contact_phone THEN
|
|
v_changes := v_changes || jsonb_build_object('contact_phone', jsonb_build_object('old', OLD.contact_phone, 'new', NEW.contact_phone));
|
|
END IF;
|
|
|
|
-- Skip if nothing actually changed
|
|
IF v_changes = '{}'::jsonb THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
-- Classify the action
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_action := 'status_changed';
|
|
END IF;
|
|
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
auth.uid()
|
|
);
|
|
|
|
INSERT INTO public.event_audit_log (event_id, account_id, user_id, action, changes)
|
|
VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);
|
|
|
|
RETURN NULL;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_events_audit_on_update
|
|
AFTER UPDATE ON public.events
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_event_audit_on_update();
|
|
|
|
-- C.3 Bookings UPDATE trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_booking_audit_on_update()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_changes jsonb := '{}'::jsonb;
|
|
v_action text := 'updated';
|
|
v_user_id uuid;
|
|
BEGIN
|
|
-- Build changes diff (field by field)
|
|
IF OLD.room_id IS DISTINCT FROM NEW.room_id THEN
|
|
v_changes := v_changes || jsonb_build_object('room_id', jsonb_build_object('old', OLD.room_id, 'new', NEW.room_id));
|
|
END IF;
|
|
IF OLD.guest_id IS DISTINCT FROM NEW.guest_id THEN
|
|
v_changes := v_changes || jsonb_build_object('guest_id', jsonb_build_object('old', OLD.guest_id, 'new', NEW.guest_id));
|
|
END IF;
|
|
IF OLD.check_in IS DISTINCT FROM NEW.check_in THEN
|
|
v_changes := v_changes || jsonb_build_object('check_in', jsonb_build_object('old', OLD.check_in, 'new', NEW.check_in));
|
|
END IF;
|
|
IF OLD.check_out IS DISTINCT FROM NEW.check_out THEN
|
|
v_changes := v_changes || jsonb_build_object('check_out', jsonb_build_object('old', OLD.check_out, 'new', NEW.check_out));
|
|
END IF;
|
|
IF OLD.adults IS DISTINCT FROM NEW.adults THEN
|
|
v_changes := v_changes || jsonb_build_object('adults', jsonb_build_object('old', OLD.adults, 'new', NEW.adults));
|
|
END IF;
|
|
IF OLD.children IS DISTINCT FROM NEW.children THEN
|
|
v_changes := v_changes || jsonb_build_object('children', jsonb_build_object('old', OLD.children, 'new', NEW.children));
|
|
END IF;
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_changes := v_changes || jsonb_build_object('status', jsonb_build_object('old', OLD.status, 'new', NEW.status));
|
|
END IF;
|
|
IF OLD.total_price IS DISTINCT FROM NEW.total_price THEN
|
|
v_changes := v_changes || jsonb_build_object('total_price', jsonb_build_object('old', OLD.total_price, 'new', NEW.total_price));
|
|
END IF;
|
|
IF OLD.notes IS DISTINCT FROM NEW.notes THEN
|
|
v_changes := v_changes || jsonb_build_object('notes', jsonb_build_object('old', OLD.notes, 'new', NEW.notes));
|
|
END IF;
|
|
|
|
-- Skip if nothing actually changed
|
|
IF v_changes = '{}'::jsonb THEN
|
|
RETURN NULL;
|
|
END IF;
|
|
|
|
-- Classify the action
|
|
IF OLD.status IS DISTINCT FROM NEW.status THEN
|
|
v_action := 'status_changed';
|
|
END IF;
|
|
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
auth.uid()
|
|
);
|
|
|
|
INSERT INTO public.booking_audit_log (booking_id, account_id, user_id, action, changes)
|
|
VALUES (NEW.id, NEW.account_id, v_user_id, v_action, v_changes);
|
|
|
|
RETURN NULL;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_bookings_audit_on_update
|
|
AFTER UPDATE ON public.bookings
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_booking_audit_on_update();
|
|
|
|
-- -------------------------------------------------------
|
|
-- D) Auto-audit triggers for INSERT
|
|
-- -------------------------------------------------------
|
|
|
|
-- D.1 Courses INSERT trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_course_audit_on_insert()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_user_id uuid;
|
|
BEGIN
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
NEW.created_by
|
|
);
|
|
|
|
INSERT INTO public.course_audit_log (course_id, account_id, user_id, action, metadata)
|
|
VALUES (
|
|
NEW.id, NEW.account_id, v_user_id, 'created',
|
|
jsonb_build_object(
|
|
'course_number', NEW.course_number,
|
|
'name', NEW.name,
|
|
'status', NEW.status,
|
|
'fee', NEW.fee,
|
|
'capacity', NEW.capacity,
|
|
'start_date', NEW.start_date,
|
|
'end_date', NEW.end_date
|
|
)
|
|
);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_courses_audit_on_insert
|
|
AFTER INSERT ON public.courses
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_course_audit_on_insert();
|
|
|
|
-- D.2 Events INSERT trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_event_audit_on_insert()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_user_id uuid;
|
|
BEGIN
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
NEW.created_by
|
|
);
|
|
|
|
INSERT INTO public.event_audit_log (event_id, account_id, user_id, action, metadata)
|
|
VALUES (
|
|
NEW.id, NEW.account_id, v_user_id, 'created',
|
|
jsonb_build_object(
|
|
'name', NEW.name,
|
|
'status', NEW.status,
|
|
'event_date', NEW.event_date,
|
|
'location', NEW.location,
|
|
'capacity', NEW.capacity,
|
|
'fee', NEW.fee
|
|
)
|
|
);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_events_audit_on_insert
|
|
AFTER INSERT ON public.events
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_event_audit_on_insert();
|
|
|
|
-- D.3 Bookings INSERT trigger
|
|
CREATE OR REPLACE FUNCTION public.trg_booking_audit_on_insert()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = ''
|
|
AS $$
|
|
DECLARE
|
|
v_user_id uuid;
|
|
BEGIN
|
|
v_user_id := COALESCE(
|
|
NULLIF(current_setting('app.current_user_id', true), '')::uuid,
|
|
NEW.created_by
|
|
);
|
|
|
|
INSERT INTO public.booking_audit_log (booking_id, account_id, user_id, action, metadata)
|
|
VALUES (
|
|
NEW.id, NEW.account_id, v_user_id, 'created',
|
|
jsonb_build_object(
|
|
'room_id', NEW.room_id,
|
|
'guest_id', NEW.guest_id,
|
|
'check_in', NEW.check_in,
|
|
'check_out', NEW.check_out,
|
|
'status', NEW.status,
|
|
'total_price', NEW.total_price,
|
|
'adults', NEW.adults,
|
|
'children', NEW.children
|
|
)
|
|
);
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE TRIGGER trg_bookings_audit_on_insert
|
|
AFTER INSERT ON public.bookings
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION public.trg_booking_audit_on_insert();
|