Show saved-video counts (not channel counts) in the Saved page sidebar
GET /api/feed/saved-counts aggregates, per category, how many videos have
a completed download (using each video's latest job only, same rule as
the existing ?downloaded=true feed filter). CategoryNav gains an optional
categoryCounts override so Feed/Channels keep showing channel_count while
Saved shows this instead.
Query key ['feed', 'saved-counts'] deliberately nests under 'feed' so
DownloadButton's existing invalidateQueries({queryKey: ['feed']}) on
download/delete refreshes these counts too, with no extra wiring.
1 new backend test (77 total).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
44f81f0f0d
commit
90eb012e0d
5 changed files with 85 additions and 18 deletions
|
|
@ -37,6 +37,42 @@ def _decode_cursor(cursor: str) -> tuple[datetime, int]:
|
|||
raise HTTPException(status_code=400, detail="Invalid cursor")
|
||||
|
||||
|
||||
def _completed_video_ids():
|
||||
# A video may have several download_jobs over time (retries,
|
||||
# redownload after delete); only its most recent one counts.
|
||||
latest_job_ids = select(func.max(DownloadJob.id)).group_by(DownloadJob.video_id)
|
||||
return select(DownloadJob.video_id).where(
|
||||
DownloadJob.id.in_(latest_job_ids), DownloadJob.status == "completed"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/feed/saved-counts")
|
||||
def get_saved_counts(db: Session = Depends(get_db)) -> dict:
|
||||
"""Per-category counts of saved (downloaded) videos, for the Saved
|
||||
page's sidebar -- distinct from /api/categories' channel_count."""
|
||||
completed_video_ids = _completed_video_ids()
|
||||
|
||||
all_count = db.query(Video).filter(Video.id.in_(completed_video_ids)).count()
|
||||
|
||||
categorized_channel_ids = select(channel_categories.c.channel_id)
|
||||
uncategorized_count = (
|
||||
db.query(Video)
|
||||
.filter(Video.id.in_(completed_video_ids), ~Video.channel_id.in_(categorized_channel_ids))
|
||||
.count()
|
||||
)
|
||||
|
||||
rows = (
|
||||
db.query(channel_categories.c.category_id, func.count(func.distinct(Video.id)))
|
||||
.join(Video, Video.channel_id == channel_categories.c.channel_id)
|
||||
.filter(Video.id.in_(completed_video_ids))
|
||||
.group_by(channel_categories.c.category_id)
|
||||
.all()
|
||||
)
|
||||
categories = {str(category_id): count for category_id, count in rows}
|
||||
|
||||
return {"all": all_count, "uncategorized": uncategorized_count, "categories": categories}
|
||||
|
||||
|
||||
@router.get("/feed")
|
||||
def get_feed(
|
||||
category_id: int | None = None,
|
||||
|
|
@ -61,13 +97,7 @@ def get_feed(
|
|||
query = query.filter(Video.channel_id.in_(channel_ids_in_category))
|
||||
|
||||
if downloaded:
|
||||
# A video may have several download_jobs over time (retries,
|
||||
# redownload after delete); only its most recent one counts.
|
||||
latest_job_ids = select(func.max(DownloadJob.id)).group_by(DownloadJob.video_id)
|
||||
completed_video_ids = select(DownloadJob.video_id).where(
|
||||
DownloadJob.id.in_(latest_job_ids), DownloadJob.status == "completed"
|
||||
)
|
||||
query = query.filter(Video.id.in_(completed_video_ids))
|
||||
query = query.filter(Video.id.in_(_completed_video_ids()))
|
||||
|
||||
if cursor:
|
||||
cursor_published_at, cursor_id = _decode_cursor(cursor)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue