98 lines
4.7 KiB
SQL
98 lines
4.7 KiB
SQL
-- Audit Logs Migration
|
|
-- Tracks all admin actions: article edits, user role changes, publish events, deletions
|
|
|
|
-- 1. Create audit_action_type enum
|
|
DROP TYPE IF EXISTS public.audit_action_type CASCADE;
|
|
CREATE TYPE public.audit_action_type AS ENUM (
|
|
'article_created',
|
|
'article_edited',
|
|
'article_published',
|
|
'article_unpublished',
|
|
'article_deleted',
|
|
'article_archived',
|
|
'article_status_changed',
|
|
'user_role_changed',
|
|
'user_activated',
|
|
'user_deactivated',
|
|
'user_invited',
|
|
'user_invitation_revoked',
|
|
'import_completed',
|
|
'import_failed'
|
|
);
|
|
|
|
-- 2. Create audit_logs table
|
|
CREATE TABLE IF NOT EXISTS public.audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
action public.audit_action_type NOT NULL,
|
|
performed_by UUID REFERENCES public.user_profiles(id) ON DELETE SET NULL,
|
|
performed_by_email TEXT,
|
|
affected_item_id TEXT,
|
|
affected_item_type TEXT,
|
|
affected_item_title TEXT,
|
|
old_value JSONB,
|
|
new_value JSONB,
|
|
metadata JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- 3. Indexes
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON public.audit_logs(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_performed_by ON public.audit_logs(performed_by);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON public.audit_logs(action);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_logs_affected_item_id ON public.audit_logs(affected_item_id);
|
|
|
|
-- 4. Enable RLS
|
|
ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- 5. RLS Policies — only admins and editors can read; system inserts via service role
|
|
DROP POLICY IF EXISTS "admins_read_audit_logs" ON public.audit_logs;
|
|
CREATE POLICY "admins_read_audit_logs"
|
|
ON public.audit_logs
|
|
FOR SELECT
|
|
TO authenticated
|
|
USING (
|
|
EXISTS (
|
|
SELECT 1 FROM public.user_profiles up
|
|
WHERE up.id = auth.uid() AND up.role IN ('admin', 'editor')
|
|
)
|
|
);
|
|
|
|
DROP POLICY IF EXISTS "authenticated_insert_audit_logs" ON public.audit_logs;
|
|
CREATE POLICY "authenticated_insert_audit_logs"
|
|
ON public.audit_logs
|
|
FOR INSERT
|
|
TO authenticated
|
|
WITH CHECK (true);
|
|
|
|
-- 6. Seed sample audit log data for demonstration
|
|
DO $$
|
|
DECLARE
|
|
existing_user_id UUID;
|
|
existing_user_email TEXT;
|
|
BEGIN
|
|
IF EXISTS (
|
|
SELECT 1 FROM information_schema.tables
|
|
WHERE table_schema = 'public' AND table_name = 'user_profiles'
|
|
) THEN
|
|
SELECT id, email INTO existing_user_id, existing_user_email
|
|
FROM public.user_profiles LIMIT 1;
|
|
|
|
IF existing_user_id IS NOT NULL THEN
|
|
INSERT INTO public.audit_logs (id, action, performed_by, performed_by_email, affected_item_id, affected_item_type, affected_item_title, old_value, new_value, created_at)
|
|
VALUES
|
|
(gen_random_uuid(), 'article_published', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'article', 'Broadcast Technology Trends 2026', NULL, jsonb_build_object('status', 'published'), NOW() - INTERVAL '2 hours'),
|
|
(gen_random_uuid(), 'user_role_changed', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'user', 'jane.doe@example.com', jsonb_build_object('role', 'reader'), jsonb_build_object('role', 'contributor'), NOW() - INTERVAL '4 hours'),
|
|
(gen_random_uuid(), 'article_edited', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'article', 'IP Video Production Guide', jsonb_build_object('status', 'draft'), jsonb_build_object('status', 'draft', 'title', 'IP Video Production Guide Updated'), NOW() - INTERVAL '6 hours'),
|
|
(gen_random_uuid(), 'article_deleted', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'article', 'Old NAB Show Coverage 2020', jsonb_build_object('status', 'archived'), NULL, NOW() - INTERVAL '1 day'),
|
|
(gen_random_uuid(), 'user_invited', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'user', 'newcontributor@example.com', NULL, jsonb_build_object('role', 'contributor'), NOW() - INTERVAL '1 day 2 hours'),
|
|
(gen_random_uuid(), 'article_status_changed', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'article', 'Cloud Production Workflows', jsonb_build_object('status', 'pending'), jsonb_build_object('status', 'published'), NOW() - INTERVAL '2 days'),
|
|
(gen_random_uuid(), 'import_completed', existing_user_id, existing_user_email, NULL, 'import', 'WordPress Import Batch', NULL, jsonb_build_object('imported_count', 42, 'failed_count', 2), NOW() - INTERVAL '3 days'),
|
|
(gen_random_uuid(), 'user_deactivated', existing_user_id, existing_user_email, gen_random_uuid()::TEXT, 'user', 'spammer@example.com', jsonb_build_object('is_active', true), jsonb_build_object('is_active', false), NOW() - INTERVAL '4 days')
|
|
ON CONFLICT (id) DO NOTHING;
|
|
END IF;
|
|
END IF;
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE 'Audit log seed data failed: %', SQLERRM;
|
|
END $$;
|