diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index 69b14c91c..c98576690 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -3385,7 +3385,7 @@ "description": "Create triggers to auto-log changes to audit_log table", "owner": "backend", "estimated_hours": 6, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -3406,7 +3406,9 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-24T15:47:37.382430", + "implementation_notes": "Created migration 052_audit_triggers.sql with a generic audit_trigger_function() that logs INSERT, UPDATE, and DELETE operations to audit_logs table. Added triggers for: users, tracks, playlists, playlist_tracks, track_likes, track_comments, marketplace_products, marketplace_orders, follows, user_blocks, playlist_collaborators. The function captures old_data and new_data as JSONB for detailed audit trails." }, { "id": "BE-DB-015", diff --git a/veza-backend-api/migrations/052_audit_triggers.sql b/veza-backend-api/migrations/052_audit_triggers.sql new file mode 100644 index 000000000..e03257150 --- /dev/null +++ b/veza-backend-api/migrations/052_audit_triggers.sql @@ -0,0 +1,193 @@ +-- 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 + IF TG_OP = 'INSERT' THEN + v_action := 'create'; + v_new_data := to_jsonb(NEW); + v_old_data := NULL; + -- Try to get user_id from NEW record (common column names) + IF NEW.creator_id IS NOT NULL THEN + v_user_id := NEW.creator_id; + ELSIF NEW.user_id IS NOT NULL THEN + v_user_id := NEW.user_id; + 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); + -- Try to get user_id from NEW record + IF NEW.creator_id IS NOT NULL THEN + v_user_id := NEW.creator_id; + ELSIF NEW.user_id IS NOT NULL THEN + v_user_id := NEW.user_id; + 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; + -- Try to get user_id from OLD record + IF OLD.creator_id IS NOT NULL THEN + v_user_id := OLD.creator_id; + ELSIF OLD.user_id IS NOT NULL THEN + v_user_id := OLD.user_id; + 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 === +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'; + +-- === TRIGGERS FOR MARKETPLACE_ORDERS TABLE === +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'; + +-- === 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 +