feat(playlists): YouTube playlist read-sync (backend)
Mirror each user's own YouTube playlists into local source='youtube' playlists, one-way (YT -> local). New client methods iter_my_playlists / iter_my_playlist_video_ids (OAuth, so private playlists work); sync/playlists.py reconciles the mirror (matching YT order) and ingests any playlist videos not in the shared catalog yet (with stub channels). POST /api/playlists/sync-youtube for manual sync (read-scope gated, per-user quota) plus a scheduler job (playlist_sync_minutes, default 6h) that syncs all read-scope users. YouTube's Watch Later / History are not API-accessible and are never synced.
This commit is contained in:
parent
cbf5037374
commit
240b6fb611
5 changed files with 228 additions and 1 deletions
|
|
@ -5,10 +5,12 @@ from fastapi import APIRouter, Depends, HTTPException
|
|||
from sqlalchemy import func, select, and_
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
|
||||
from app.auth import current_user
|
||||
from app import quota
|
||||
from app.auth import current_user, has_read_scope
|
||||
from app.db import get_db
|
||||
from app.models import Channel, Playlist, PlaylistItem, User, Video, VideoState
|
||||
from app.routes.feed import _saved_expr, _serialize
|
||||
from app.sync.playlists import sync_user_playlists
|
||||
|
||||
router = APIRouter(prefix="/api/playlists", tags=["playlists"])
|
||||
|
||||
|
|
@ -187,6 +189,22 @@ def remove_watch_later(
|
|||
return {"saved": False}
|
||||
|
||||
|
||||
@router.post("/sync-youtube")
|
||||
def sync_youtube(
|
||||
user: User = Depends(current_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
"""Pull the user's YouTube playlists into local mirrors now (read-only direction).
|
||||
Also runs automatically on the scheduler; this is the manual 'sync now'."""
|
||||
if not has_read_scope(user):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Connect YouTube (read access) to sync your playlists.",
|
||||
)
|
||||
with quota.attribute(user.id, "playlist_sync"):
|
||||
return sync_user_playlists(db, user)
|
||||
|
||||
|
||||
@router.get("/{playlist_id}")
|
||||
def get_playlist(
|
||||
playlist_id: int,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue