diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index c09000ce4..69b14c91c 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -3350,7 +3350,7 @@ "description": "Create views for user_stats, track_stats, playlist_stats", "owner": "backend", "estimated_hours": 5, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -3371,7 +3371,9 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-24T15:46:28.058411", + "implementation_notes": "Created migration 051_stats_views.sql with three views: user_stats (aggregates user data from tracks, playlists, follows, track_plays, track_likes, track_comments), track_stats (aggregates track data from track_plays, playback_analytics, track_shares), and playlist_stats (aggregates playlist data from playlist_tracks, tracks, playlist_collaborators, playlist_share_links). All views exclude soft-deleted records and include comprehensive statistics." }, { "id": "BE-DB-014", diff --git a/veza-backend-api/migrations/051_stats_views.sql b/veza-backend-api/migrations/051_stats_views.sql new file mode 100644 index 000000000..a3760c926 --- /dev/null +++ b/veza-backend-api/migrations/051_stats_views.sql @@ -0,0 +1,144 @@ +-- 051_stats_views.sql +-- Database Views for Common Queries (BE-DB-013: Add database views for common queries) +-- Create views for user_stats, track_stats, playlist_stats + +-- === USER_STATS VIEW === +-- View for user statistics aggregating data from multiple tables +CREATE OR REPLACE VIEW public.user_stats AS +SELECT + u.id AS user_id, + u.username, + u.email, + u.created_at AS user_created_at, + + -- Track statistics + COUNT(DISTINCT t.id) AS tracks_count, + COALESCE(SUM(t.play_count), 0) AS total_track_plays, + COALESCE(SUM(t.like_count), 0) AS total_track_likes, + COALESCE(SUM(t.download_count), 0) AS total_track_downloads, + + -- Playlist statistics + COUNT(DISTINCT p.id) AS playlists_count, + COALESCE(SUM(p.track_count), 0) AS total_playlist_tracks, + COALESCE(SUM(p.follower_count), 0) AS total_playlist_followers, + + -- Social statistics + COUNT(DISTINCT f1.id) AS followers_count, + COUNT(DISTINCT f2.id) AS following_count, + + -- Playback statistics + COUNT(DISTINCT tp.track_id) AS unique_tracks_played, + COUNT(tp.id) AS total_plays, + COALESCE(SUM(tp.duration), 0) AS total_play_duration, + CASE + WHEN COUNT(tp.id) > 0 THEN COALESCE(SUM(tp.duration), 0)::FLOAT / COUNT(tp.id) + ELSE 0 + END AS average_play_duration, + + -- Like statistics + COUNT(DISTINCT tl.track_id) AS liked_tracks_count, + + -- Comment statistics + COUNT(DISTINCT tc.id) AS comments_count + +FROM public.users u +LEFT JOIN public.tracks t ON t.creator_id = u.id AND t.deleted_at IS NULL +LEFT JOIN public.playlists p ON p.user_id = u.id AND p.deleted_at IS NULL +LEFT JOIN public.follows f1 ON f1.followed_id = u.id AND f1.deleted_at IS NULL +LEFT JOIN public.follows f2 ON f2.follower_id = u.id AND f2.deleted_at IS NULL +LEFT JOIN public.track_plays tp ON tp.user_id = u.id +LEFT JOIN public.track_likes tl ON tl.user_id = u.id +LEFT JOIN public.track_comments tc ON tc.user_id = u.id AND tc.deleted_at IS NULL +WHERE u.deleted_at IS NULL +GROUP BY u.id, u.username, u.email, u.created_at; + +COMMENT ON VIEW public.user_stats IS 'Aggregated user statistics view including tracks, playlists, social, and playback metrics'; + +-- === TRACK_STATS VIEW === +-- View for track statistics aggregating plays, likes, comments, and downloads +CREATE OR REPLACE VIEW public.track_stats AS +SELECT + t.id AS track_id, + t.title, + t.artist, + t.creator_id, + t.created_at AS track_created_at, + t.published_at, + + -- Denormalized counts from tracks table + t.play_count AS views, + t.like_count AS likes, + t.comment_count AS comments, + t.download_count AS downloads, + + -- Playback statistics + COUNT(DISTINCT tp.user_id) AS unique_listeners, + COUNT(tp.id) AS total_plays, + COALESCE(SUM(tp.duration), 0) AS total_play_time, + CASE + WHEN COUNT(tp.id) > 0 THEN COALESCE(SUM(tp.duration), 0)::FLOAT / COUNT(tp.id) + ELSE 0 + END AS average_duration, + + -- Playback analytics + COALESCE(AVG(pa.completion_rate), 0) AS average_completion_rate, + COALESCE(SUM(pa.play_time), 0) AS total_analytics_play_time, + COALESCE(SUM(pa.pause_count), 0) AS total_pause_count, + COALESCE(SUM(pa.seek_count), 0) AS total_seek_count, + + -- Share statistics + COUNT(DISTINCT ts.id) AS share_links_count, + COALESCE(SUM(ts.access_count), 0) AS total_share_accesses + +FROM public.tracks t +LEFT JOIN public.track_plays tp ON tp.track_id = t.id +LEFT JOIN public.playback_analytics pa ON pa.track_id = t.id +LEFT JOIN public.track_shares ts ON ts.track_id = t.id AND ts.deleted_at IS NULL +WHERE t.deleted_at IS NULL +GROUP BY t.id, t.title, t.artist, t.creator_id, t.created_at, t.published_at, + t.play_count, t.like_count, t.comment_count, t.download_count; + +COMMENT ON VIEW public.track_stats IS 'Aggregated track statistics view including plays, likes, comments, downloads, and playback analytics'; + +-- === PLAYLIST_STATS VIEW === +-- View for playlist statistics aggregating tracks, followers, and plays +CREATE OR REPLACE VIEW public.playlist_stats AS +SELECT + p.id AS playlist_id, + p.name AS playlist_name, + p.user_id AS creator_id, + p.created_at AS playlist_created_at, + p.updated_at AS playlist_updated_at, + + -- Denormalized counts from playlists table + p.track_count AS tracks_count, + p.follower_count AS followers_count, + + -- Track statistics (aggregated from playlist_tracks) + COUNT(DISTINCT pt.track_id) AS actual_track_count, + COALESCE(SUM(t.play_count), 0) AS total_track_plays, + COALESCE(SUM(t.like_count), 0) AS total_track_likes, + COALESCE(AVG(t.duration), 0) AS average_track_duration, + COALESCE(SUM(t.duration), 0) AS total_playlist_duration, + + -- Collaboration statistics + COUNT(DISTINCT pc.user_id) AS collaborators_count, + + -- Share statistics + COUNT(DISTINCT psl.id) AS share_links_count, + COALESCE(SUM(psl.access_count), 0) AS total_share_accesses + +FROM public.playlists p +LEFT JOIN public.playlist_tracks pt ON pt.playlist_id = p.id +LEFT JOIN public.tracks t ON t.id = pt.track_id AND t.deleted_at IS NULL +LEFT JOIN public.playlist_collaborators pc ON pc.playlist_id = p.id AND pc.deleted_at IS NULL +LEFT JOIN public.playlist_share_links psl ON psl.playlist_id = p.id AND psl.deleted_at IS NULL +WHERE p.deleted_at IS NULL +GROUP BY p.id, p.name, p.user_id, p.created_at, p.updated_at, + p.track_count, p.follower_count; + +COMMENT ON VIEW public.playlist_stats IS 'Aggregated playlist statistics view including tracks, followers, plays, and collaboration metrics'; + +-- Indexes for better query performance (if needed) +-- Note: Views don't support indexes directly, but indexes on underlying tables will help +