Popular tab now shows only flagged popular videos in rank order

Add channel_popular_videos table (channel_id, video_id, rank).
_fetch_popular_task clears and rewrites this table after each fetch.
GET /channels/{id}/videos?sort=popular now JOINs this table and orders
by rank instead of view_count, so the tab shows exactly the videos
YouTube returned in popularity order — nothing more.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 22:38:53 +02:00
parent 2f37072187
commit 112f87e764
2 changed files with 66 additions and 24 deletions

View File

@@ -87,6 +87,14 @@ def on_startup():
crawled_at DATETIME DEFAULT CURRENT_TIMESTAMP
)""",
"ALTER TABLE playlists ADD COLUMN video_ids TEXT",
"""CREATE TABLE IF NOT EXISTS channel_popular_videos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
video_id INTEGER NOT NULL REFERENCES videos(id) ON DELETE CASCADE,
rank INTEGER NOT NULL,
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(channel_id, video_id)
)""",
"""CREATE TABLE IF NOT EXISTS search_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,

View File

@@ -612,32 +612,49 @@ def get_channel_videos(
current_user: User = Depends(get_current_user),
):
_get_channel_or_404(db, channel_id)
order = {
"newest": "v.published_at DESC NULLS LAST",
"oldest": "v.published_at ASC NULLS LAST",
"title": "v.title ASC",
"unwatched":"COALESCE(uv.watched, 0) ASC, v.published_at DESC NULLS LAST",
"popular": "v.view_count DESC NULLS LAST",
}.get(sort, "v.published_at DESC NULLS LAST")
params: dict = {"user_id": current_user.id, "channel_id": channel_id, "limit": limit, "offset": offset}
q_clause = ""
if q.strip():
q_clause = "AND (v.title LIKE :q OR v.description LIKE :q)"
params["q"] = f"%{q.strip()}%"
rows = db.execute(
text(f"""
SELECT v.id, v.youtube_video_id, v.title, v.thumbnail_url,
v.duration_seconds, v.published_at, v.view_count,
COALESCE(uv.downloaded, 0) AS is_downloaded,
COALESCE(uv.watched, 0) AS is_watched
FROM videos v
LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
WHERE v.channel_id = :channel_id {q_clause}
ORDER BY {order}
LIMIT :limit OFFSET :offset
"""),
params,
).mappings().all()
if sort == "popular":
rows = db.execute(
text(f"""
SELECT v.id, v.youtube_video_id, v.title, v.thumbnail_url,
v.duration_seconds, v.published_at, v.view_count,
COALESCE(uv.downloaded, 0) AS is_downloaded,
COALESCE(uv.watched, 0) AS is_watched
FROM channel_popular_videos cpv
JOIN videos v ON cpv.video_id = v.id
LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
WHERE cpv.channel_id = :channel_id {q_clause}
ORDER BY cpv.rank ASC
LIMIT :limit OFFSET :offset
"""),
params,
).mappings().all()
else:
order = {
"newest": "v.published_at DESC NULLS LAST",
"oldest": "v.published_at ASC NULLS LAST",
"title": "v.title ASC",
"unwatched":"COALESCE(uv.watched, 0) ASC, v.published_at DESC NULLS LAST",
}.get(sort, "v.published_at DESC NULLS LAST")
rows = db.execute(
text(f"""
SELECT v.id, v.youtube_video_id, v.title, v.thumbnail_url,
v.duration_seconds, v.published_at, v.view_count,
COALESCE(uv.downloaded, 0) AS is_downloaded,
COALESCE(uv.watched, 0) AS is_watched
FROM videos v
LEFT JOIN user_videos uv ON v.id = uv.video_id AND uv.user_id = :user_id
WHERE v.channel_id = :channel_id {q_clause}
ORDER BY {order}
LIMIT :limit OFFSET :offset
"""),
params,
).mappings().all()
return [VideoOut(**dict(r)) for r in rows]
@@ -705,7 +722,12 @@ def _fetch_popular_task(channel_id: int, youtube_channel_id: str):
channel = db.query(Channel).filter_by(id=channel_id).first()
if not channel:
return
for yt_id in video_ids:
# Clear previous popular list for this channel
db.execute(text("DELETE FROM channel_popular_videos WHERE channel_id = :cid"), {"cid": channel_id})
db.commit()
for rank, yt_id in enumerate(video_ids, start=1):
meta = results.get(yt_id)
if not meta:
continue
@@ -716,8 +738,9 @@ def _fetch_popular_task(channel_id: int, youtube_channel_id: str):
existing.view_count = meta["view_count"]
if meta.get("published_at") and not existing.published_at:
existing.published_at = meta["published_at"]
video_id = existing.id
else:
db.add(Video(
v = Video(
youtube_video_id=yt_id,
channel_id=channel.id,
title=meta.get("title", ""),
@@ -726,7 +749,18 @@ def _fetch_popular_task(channel_id: int, youtube_channel_id: str):
published_at=meta.get("published_at"),
tags=meta.get("tags") or "[]",
view_count=meta.get("view_count"),
))
)
db.add(v)
db.flush()
video_id = v.id
db.execute(
text("""
INSERT INTO channel_popular_videos (channel_id, video_id, rank)
VALUES (:cid, :vid, :rank)
ON CONFLICT(channel_id, video_id) DO UPDATE SET rank = :rank
"""),
{"cid": channel_id, "vid": video_id, "rank": rank},
)
db.commit()
except Exception:
db.rollback()