-- ============================================================================ -- JellyStat API Functions -- Parameterized database functions callable by the API layer as: -- SELECT * FROM fn_user_watch_history('user_id_here', 10080); -- SELECT * FROM fn_user_genre_summary('user_id_here', 10080); -- SELECT * FROM fn_user_summary('user_id_here'); -- ============================================================================ -- ---------------------------------------------------------------------------- -- 1. User Watch History -- Returns every distinct title watched in the last N minutes, -- grouped and summed by title, ordered by most-watched first. -- ---------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION public.fn_user_watch_history( p_user_id TEXT, p_minutes INTEGER DEFAULT 10080 -- 7 days in minutes ) RETURNS TABLE( title TEXT, watch_time_sec NUMERIC, media_type TEXT ) LANGUAGE sql STABLE AS $$ SELECT COALESCE(a."SeriesName", a."NowPlayingItemName") AS title, SUM(a."PlaybackDuration")::NUMERIC AS watch_time_sec, CASE WHEN a."SeriesName" IS NOT NULL THEN 'series' ELSE 'movie' END AS media_type FROM jf_playback_activity a WHERE a."UserId" = p_user_id AND a."ActivityDateInserted" >= NOW() - (p_minutes * INTERVAL '1 minute') GROUP BY COALESCE(a."SeriesName", a."NowPlayingItemName"), CASE WHEN a."SeriesName" IS NOT NULL THEN 'series' ELSE 'movie' END ORDER BY watch_time_sec DESC; $$; -- ---------------------------------------------------------------------------- -- 2. Genre Summary -- Returns total watch time per genre for a user over the last N minutes. -- Resolves genres for both movies (directly on the item) and series -- episodes (via jf_library_episodes → jf_library_items chain). -- ---------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION public.fn_user_genre_summary( p_user_id TEXT, p_minutes INTEGER DEFAULT 10080 ) RETURNS TABLE( genre TEXT, watch_time_sec NUMERIC ) LANGUAGE sql STABLE AS $$ WITH movie_genres AS ( -- Movies: join playback directly to library_items on NowPlayingItemId SELECT genre_item.value AS genre, SUM(a."PlaybackDuration") AS watch_time_sec FROM jf_playback_activity a JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId" CROSS JOIN LATERAL jsonb_array_elements_text(i."Genres") AS genre_item(value) WHERE a."UserId" = p_user_id AND a."SeriesName" IS NULL -- movies only AND a."ActivityDateInserted" >= NOW() - (p_minutes * INTERVAL '1 minute') AND i."Genres" IS NOT NULL AND jsonb_array_length(i."Genres") > 0 GROUP BY genre_item.value ), series_genres AS ( -- Series: playback → episodes → series item → genres SELECT genre_item.value AS genre, SUM(a."PlaybackDuration") AS watch_time_sec FROM jf_playback_activity a JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId" JOIN jf_library_items i ON i."Id" = e."SeriesId" CROSS JOIN LATERAL jsonb_array_elements_text(i."Genres") AS genre_item(value) WHERE a."UserId" = p_user_id AND a."SeriesName" IS NOT NULL -- TV episodes only AND a."ActivityDateInserted" >= NOW() - (p_minutes * INTERVAL '1 minute') AND i."Genres" IS NOT NULL AND jsonb_array_length(i."Genres") > 0 GROUP BY genre_item.value ), combined AS ( SELECT genre, watch_time_sec FROM movie_genres UNION ALL SELECT genre, watch_time_sec FROM series_genres ) SELECT genre, SUM(watch_time_sec)::NUMERIC AS watch_time_sec FROM combined GROUP BY genre ORDER BY watch_time_sec DESC; $$; -- ---------------------------------------------------------------------------- -- 3. User Summary -- One-shot dashboard: all-time stats + recent windows + top genres. -- Returns key-value rows that the API trivially converts to a JSON object -- with Object.fromEntries() or similar. -- ---------------------------------------------------------------------------- CREATE OR REPLACE FUNCTION public.fn_user_summary( p_user_id TEXT ) RETURNS TABLE( metric TEXT, value JSONB ) LANGUAGE sql STABLE AS $$ -- total_watch_time (all time) SELECT 'total_watch_time'::TEXT AS metric, to_jsonb(COALESCE(SUM("PlaybackDuration"), 0)::NUMERIC) AS value FROM jf_playback_activity WHERE "UserId" = p_user_id UNION ALL -- most_watched_series (by total watch time) SELECT 'most_watched_series'::TEXT AS metric, COALESCE( (SELECT to_jsonb("SeriesName") FROM jf_playback_activity WHERE "UserId" = p_user_id AND "SeriesName" IS NOT NULL GROUP BY "SeriesName" ORDER BY SUM("PlaybackDuration") DESC LIMIT 1), 'null'::JSONB ) AS value UNION ALL -- most_watched_movie (by total watch time) SELECT 'most_watched_movie'::TEXT AS metric, COALESCE( (SELECT to_jsonb("NowPlayingItemName") FROM jf_playback_activity WHERE "UserId" = p_user_id AND "SeriesName" IS NULL GROUP BY "NowPlayingItemName" ORDER BY SUM("PlaybackDuration") DESC LIMIT 1), 'null'::JSONB ) AS value UNION ALL -- total_watch_time_last_month (last 30 days) SELECT 'total_last_30d'::TEXT AS metric, to_jsonb(COALESCE(SUM("PlaybackDuration"), 0)::NUMERIC) AS value FROM jf_playback_activity WHERE "UserId" = p_user_id AND "ActivityDateInserted" >= NOW() - INTERVAL '30 days' UNION ALL -- total_watch_time_last_week (last 7 days) SELECT 'total_last_7d'::TEXT AS metric, to_jsonb(COALESCE(SUM("PlaybackDuration"), 0)::NUMERIC) AS value FROM jf_playback_activity WHERE "UserId" = p_user_id AND "ActivityDateInserted" >= NOW() - INTERVAL '7 days' UNION ALL -- top_genres (top 3 all-time, as a JSON array) SELECT 'top_genres'::TEXT AS metric, COALESCE( (SELECT jsonb_agg(genre ORDER BY watch_time_sec DESC) FROM ( SELECT genre, SUM(watch_time_sec) AS watch_time_sec FROM ( -- movies SELECT genre_item.value AS genre, SUM(a."PlaybackDuration") AS watch_time_sec FROM jf_playback_activity a JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId" CROSS JOIN LATERAL jsonb_array_elements_text(i."Genres") AS genre_item(value) WHERE a."UserId" = p_user_id AND a."SeriesName" IS NULL AND i."Genres" IS NOT NULL AND jsonb_array_length(i."Genres") > 0 GROUP BY genre_item.value UNION ALL -- series SELECT genre_item.value AS genre, SUM(a."PlaybackDuration") AS watch_time_sec FROM jf_playback_activity a JOIN jf_library_episodes e ON e."EpisodeId" = a."EpisodeId" JOIN jf_library_items i ON i."Id" = e."SeriesId" CROSS JOIN LATERAL jsonb_array_elements_text(i."Genres") AS genre_item(value) WHERE a."UserId" = p_user_id AND a."SeriesName" IS NOT NULL AND i."Genres" IS NOT NULL AND jsonb_array_length(i."Genres") > 0 GROUP BY genre_item.value ) combined GROUP BY genre ORDER BY SUM(watch_time_sec) DESC LIMIT 3 ) top3 ), '[]'::JSONB ) AS value; $$;