Hide unsubscribed channels from the channels list, add a Saved tab
- GET /api/channels called with subscribed=true from the Channels page, so a channel disappears from the list as soon as it's unsubscribed instead of lingering with a badge - GET /api/feed gains a `downloaded` filter (this was already anticipated in the original TZ's feed query params but left unimplemented until download_jobs existed) -- matches on each video's *latest* job only, so a redownload/delete history doesn't leave stale matches - New /saved route: same category filtering, video grid and download actions as the main feed, scoped to downloaded videos only 2 new backend tests (76 total). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
62f8fb746b
commit
b29bcfb8d7
7 changed files with 127 additions and 3 deletions
|
|
@ -2,13 +2,14 @@ import base64
|
|||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.auth_dependency import require_session
|
||||
from app.db import get_db
|
||||
from app.models.channel import Channel
|
||||
from app.models.channel_category import channel_categories
|
||||
from app.models.download_job import DownloadJob
|
||||
from app.models.video import Video
|
||||
from app.services.download_jobs import latest_jobs_map
|
||||
from app.services.video_presentation import channel_categories_map, serialize_video
|
||||
|
|
@ -41,6 +42,7 @@ def get_feed(
|
|||
category_id: int | None = None,
|
||||
uncategorized: bool = False,
|
||||
channel_id: int | None = None,
|
||||
downloaded: bool = False,
|
||||
limit: int = Query(DEFAULT_LIMIT, ge=1, le=MAX_LIMIT),
|
||||
cursor: str | None = None,
|
||||
db: Session = Depends(get_db),
|
||||
|
|
@ -58,6 +60,15 @@ 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))
|
||||
|
||||
if cursor:
|
||||
cursor_published_at, cursor_id = _decode_cursor(cursor)
|
||||
query = query.filter(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue