-- 052_audit_triggers.sql -- Database Triggers for Audit Logging (BE-DB-014: Add database triggers for audit logging) -- Create triggers to auto-log changes to audit_logs table -- === HELPER FUNCTION === -- Function to create audit log entries from trigger context CREATE OR REPLACE FUNCTION public.audit_trigger_function() RETURNS TRIGGER AS $$ DECLARE v_user_id UUID; v_action TEXT; v_old_data JSONB; v_new_data JSONB; BEGIN -- Determine action type -- FIX: Use JSONB extraction to safely access columns that may not exist -- This prevents errors when tables don't have creator_id or user_id columns IF TG_OP = 'INSERT' THEN v_action := 'create'; v_new_data := to_jsonb(NEW); v_old_data := NULL; -- Extract user_id from JSONB (safe - no direct column access) IF v_new_data ? 'creator_id' AND v_new_data->>'creator_id' IS NOT NULL AND v_new_data->>'creator_id' != 'null' THEN v_user_id := (v_new_data->>'creator_id')::UUID; ELSIF v_new_data ? 'user_id' AND v_new_data->>'user_id' IS NOT NULL AND v_new_data->>'user_id' != 'null' THEN v_user_id := (v_new_data->>'user_id')::UUID; ELSE v_user_id := NULL; END IF; ELSIF TG_OP = 'UPDATE' THEN v_action := 'update'; v_old_data := to_jsonb(OLD); v_new_data := to_jsonb(NEW); -- Extract user_id from JSONB IF v_new_data ? 'creator_id' AND v_new_data->>'creator_id' IS NOT NULL AND v_new_data->>'creator_id' != 'null' THEN v_user_id := (v_new_data->>'creator_id')::UUID; ELSIF v_new_data ? 'user_id' AND v_new_data->>'user_id' IS NOT NULL AND v_new_data->>'user_id' != 'null' THEN v_user_id := (v_new_data->>'user_id')::UUID; ELSE v_user_id := NULL; END IF; ELSIF TG_OP = 'DELETE' THEN v_action := 'delete'; v_old_data := to_jsonb(OLD); v_new_data := NULL; -- Extract user_id from JSONB IF v_old_data ? 'creator_id' AND v_old_data->>'creator_id' IS NOT NULL AND v_old_data->>'creator_id' != 'null' THEN v_user_id := (v_old_data->>'creator_id')::UUID; ELSIF v_old_data ? 'user_id' AND v_old_data->>'user_id' IS NOT NULL AND v_old_data->>'user_id' != 'null' THEN v_user_id := (v_old_data->>'user_id')::UUID; ELSE v_user_id := NULL; END IF; END IF; -- Insert audit log entry INSERT INTO public.audit_logs ( id, user_id, action, resource, resource_id, metadata, timestamp ) VALUES ( gen_random_uuid(), v_user_id, v_action, TG_TABLE_NAME, CASE WHEN TG_OP = 'DELETE' THEN (OLD.id)::UUID ELSE (NEW.id)::UUID END, jsonb_build_object( 'operation', TG_OP, 'old_data', v_old_data, 'new_data', v_new_data, 'table', TG_TABLE_NAME ), NOW() ); -- Return appropriate record IF TG_OP = 'DELETE' THEN RETURN OLD; ELSE RETURN NEW; END IF; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION public.audit_trigger_function() IS 'Generic audit trigger function that logs INSERT, UPDATE, and DELETE operations to audit_logs table'; -- === TRIGGERS FOR USERS TABLE === DROP TRIGGER IF EXISTS audit_users_trigger ON public.users; CREATE TRIGGER audit_users_trigger AFTER INSERT OR UPDATE OR DELETE ON public.users FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_users_trigger ON public.users IS 'Audit trigger for users table - logs all create, update, and delete operations'; -- === TRIGGERS FOR TRACKS TABLE === DROP TRIGGER IF EXISTS audit_tracks_trigger ON public.tracks; CREATE TRIGGER audit_tracks_trigger AFTER INSERT OR UPDATE OR DELETE ON public.tracks FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_tracks_trigger ON public.tracks IS 'Audit trigger for tracks table - logs all create, update, and delete operations'; -- === TRIGGERS FOR PLAYLISTS TABLE === DROP TRIGGER IF EXISTS audit_playlists_trigger ON public.playlists; CREATE TRIGGER audit_playlists_trigger AFTER INSERT OR UPDATE OR DELETE ON public.playlists FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_playlists_trigger ON public.playlists IS 'Audit trigger for playlists table - logs all create, update, and delete operations'; -- === TRIGGERS FOR PLAYLIST_TRACKS TABLE === DROP TRIGGER IF EXISTS audit_playlist_tracks_trigger ON public.playlist_tracks; CREATE TRIGGER audit_playlist_tracks_trigger AFTER INSERT OR UPDATE OR DELETE ON public.playlist_tracks FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_playlist_tracks_trigger ON public.playlist_tracks IS 'Audit trigger for playlist_tracks table - logs all create, update, and delete operations'; -- === TRIGGERS FOR TRACK_LIKES TABLE === DROP TRIGGER IF EXISTS audit_track_likes_trigger ON public.track_likes; CREATE TRIGGER audit_track_likes_trigger AFTER INSERT OR UPDATE OR DELETE ON public.track_likes FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_track_likes_trigger ON public.track_likes IS 'Audit trigger for track_likes table - logs all create, update, and delete operations'; -- === TRIGGERS FOR TRACK_COMMENTS TABLE === DROP TRIGGER IF EXISTS audit_track_comments_trigger ON public.track_comments; CREATE TRIGGER audit_track_comments_trigger AFTER INSERT OR UPDATE OR DELETE ON public.track_comments FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_track_comments_trigger ON public.track_comments IS 'Audit trigger for track_comments table - logs all create, update, and delete operations'; -- === TRIGGERS FOR MARKETPLACE_PRODUCTS TABLE === -- Note: marketplace_products table may not exist yet DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'marketplace_products' ) THEN DROP TRIGGER IF EXISTS audit_marketplace_products_trigger ON public.marketplace_products; CREATE TRIGGER audit_marketplace_products_trigger AFTER INSERT OR UPDATE OR DELETE ON public.marketplace_products FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_marketplace_products_trigger ON public.marketplace_products IS 'Audit trigger for marketplace_products table - logs all create, update, and delete operations'; END IF; END $$; -- === TRIGGERS FOR MARKETPLACE_ORDERS TABLE === -- Note: marketplace_orders table may not exist yet DO $$ BEGIN IF EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'marketplace_orders' ) THEN DROP TRIGGER IF EXISTS audit_marketplace_orders_trigger ON public.marketplace_orders; CREATE TRIGGER audit_marketplace_orders_trigger AFTER INSERT OR UPDATE OR DELETE ON public.marketplace_orders FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_marketplace_orders_trigger ON public.marketplace_orders IS 'Audit trigger for marketplace_orders table - logs all create, update, and delete operations'; END IF; END $$; -- === TRIGGERS FOR FOLLOWS TABLE === DROP TRIGGER IF EXISTS audit_follows_trigger ON public.follows; CREATE TRIGGER audit_follows_trigger AFTER INSERT OR UPDATE OR DELETE ON public.follows FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_follows_trigger ON public.follows IS 'Audit trigger for follows table - logs all create, update, and delete operations'; -- === TRIGGERS FOR USER_BLOCKS TABLE === DROP TRIGGER IF EXISTS audit_user_blocks_trigger ON public.user_blocks; CREATE TRIGGER audit_user_blocks_trigger AFTER INSERT OR UPDATE OR DELETE ON public.user_blocks FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_user_blocks_trigger ON public.user_blocks IS 'Audit trigger for user_blocks table - logs all create, update, and delete operations'; -- === TRIGGERS FOR PLAYLIST_COLLABORATORS TABLE === DROP TRIGGER IF EXISTS audit_playlist_collaborators_trigger ON public.playlist_collaborators; CREATE TRIGGER audit_playlist_collaborators_trigger AFTER INSERT OR UPDATE OR DELETE ON public.playlist_collaborators FOR EACH ROW EXECUTE FUNCTION public.audit_trigger_function(); COMMENT ON TRIGGER audit_playlist_collaborators_trigger ON public.playlist_collaborators IS 'Audit trigger for playlist_collaborators table - logs all create, update, and delete operations'; -- Note: Triggers are set to AFTER to ensure data integrity and allow access to both OLD and NEW records -- The function captures the full state of records (old and new) in JSONB format for detailed audit trails