diff --git a/backend/database.py b/backend/database.py index 51c132a..da0a373 100644 --- a/backend/database.py +++ b/backend/database.py @@ -89,6 +89,18 @@ def init_db(): _add_column_if_missing(raw_conn, "videos", "like_count", "INTEGER") _add_column_if_missing(raw_conn, "videos", "dislike_count", "INTEGER") raw_conn.commit() + # Indexes that make the channel-stats CTE query fast with many channels + for idx_sql in [ + "CREATE INDEX IF NOT EXISTS idx_videos_channel_published ON videos(channel_id, published_at DESC)", + "CREATE INDEX IF NOT EXISTS idx_videos_channel_indexed ON videos(channel_id, indexed_at)", + "CREATE INDEX IF NOT EXISTS idx_user_videos_video_user ON user_videos(video_id, user_id)", + "CREATE INDEX IF NOT EXISTS idx_user_channels_user_status ON user_channels(user_id, status)", + ]: + try: + raw_conn.execute(idx_sql) + except Exception: + pass + raw_conn.commit() # executescript handles multi-statement SQL including trigger BEGIN...END blocks raw_conn.executescript(FTS_SETUP_SQL) finally: diff --git a/backend/routers/channels.py b/backend/routers/channels.py index 4364814..261f0bb 100644 --- a/backend/routers/channels.py +++ b/backend/routers/channels.py @@ -67,31 +67,48 @@ class VideoOut(BaseModel): _CHANNEL_STATS_SELECT = """ - SELECT c.*, uc.status, uc.auto_download, uc.muted_until, uc.notes, - (SELECT COUNT(*) FROM videos WHERE channel_id = c.id) AS video_count, - (SELECT MAX(v.published_at) FROM videos v WHERE v.channel_id = c.id) AS last_published_at, - (SELECT COUNT(*) FROM videos v - LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id - WHERE v.channel_id = c.id AND COALESCE(uv.watched, 0) = 0) AS unwatched_count, - (SELECT COUNT(*) FROM videos v - JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id - WHERE v.channel_id = c.id AND uv.watched = 1) AS watched_count, - (SELECT COUNT(*) FROM videos v - JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id - WHERE v.channel_id = c.id AND uv.downloaded = 1) AS downloaded_count, - (SELECT COUNT(*) FROM videos v - WHERE v.channel_id = c.id - AND (uc.last_seen_at IS NULL OR v.indexed_at > uc.last_seen_at)) AS new_count, - (SELECT v.youtube_video_id FROM videos v - WHERE v.channel_id = c.id ORDER BY v.published_at DESC LIMIT 1) AS latest_video_id, - (SELECT v.title FROM videos v - WHERE v.channel_id = c.id ORDER BY v.published_at DESC LIMIT 1) AS latest_video_title, - (SELECT - CASE WHEN COUNT(*) < 2 THEN NULL - ELSE CAST((julianday(MAX(sub.published_at)) - julianday(MIN(sub.published_at))) AS REAL) / (COUNT(*) - 1) - END - FROM (SELECT published_at FROM videos WHERE channel_id = c.id AND published_at IS NOT NULL ORDER BY published_at DESC LIMIT 15) sub - ) AS upload_frequency_days + WITH followed AS ( + SELECT channel_id, last_seen_at + FROM user_channels + WHERE user_id = :user_id AND status = 'followed' + ), + vinfo AS ( + SELECT + v.channel_id, + COUNT(*) AS video_count, + MIN(v.published_at) AS oldest_published, + MAX(v.published_at) AS last_published_at, + SUM(CASE WHEN COALESCE(uv.watched, 0) = 0 THEN 1 ELSE 0 END) AS unwatched_count, + SUM(CASE WHEN uv.watched = 1 THEN 1 ELSE 0 END) AS watched_count, + SUM(CASE WHEN uv.downloaded = 1 THEN 1 ELSE 0 END) AS downloaded_count + FROM videos v + JOIN followed f ON f.channel_id = v.channel_id + LEFT JOIN user_videos uv ON uv.video_id = v.id AND uv.user_id = :user_id + GROUP BY v.channel_id + ), + nc AS ( + SELECT v.channel_id, COUNT(*) AS new_count + FROM videos v + JOIN followed f ON f.channel_id = v.channel_id + WHERE f.last_seen_at IS NULL OR v.indexed_at > f.last_seen_at + GROUP BY v.channel_id + ) + SELECT + c.*, uc.status, uc.auto_download, uc.muted_until, uc.notes, + COALESCE(vi.video_count, 0) AS video_count, + vi.last_published_at, + COALESCE(vi.unwatched_count, 0) AS unwatched_count, + COALESCE(vi.watched_count, 0) AS watched_count, + COALESCE(vi.downloaded_count, 0) AS downloaded_count, + COALESCE(nc.new_count, 0) AS new_count, + CASE WHEN COALESCE(vi.video_count, 0) < 2 THEN NULL + ELSE (julianday(vi.last_published_at) - julianday(vi.oldest_published)) + / (vi.video_count - 1.0) + END AS upload_frequency_days, + (SELECT v2.youtube_video_id FROM videos v2 + WHERE v2.channel_id = c.id ORDER BY v2.published_at DESC LIMIT 1) AS latest_video_id, + (SELECT v2.title FROM videos v2 + WHERE v2.channel_id = c.id ORDER BY v2.published_at DESC LIMIT 1) AS latest_video_title FROM channels c JOIN user_channels uc ON c.id = uc.channel_id WHERE uc.user_id = :user_id AND uc.status = 'followed'