Files
avbeat-com/supabase/migrations/20260321100000_audit_logs.sql
2026-05-07 16:39:17 +00:00

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 $$;