diff --git a/backend/app/routes/playlists.py b/backend/app/routes/playlists.py index 4d8e02a..d61a107 100644 --- a/backend/app/routes/playlists.py +++ b/backend/app/routes/playlists.py @@ -2,6 +2,7 @@ catalog. Foundation phase = local only (create / rename / delete, add / remove / reorder items). YouTube two-way sync (mirror existing playlists, push edits) lands in later phases.""" from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel from sqlalchemy import func, select, and_ from sqlalchemy.orm import Session, aliased @@ -197,13 +198,22 @@ def list_playlists( ] +class CreatePlaylistIn(BaseModel): + name: str + + +# Shared by the two "add a video" endpoints (Watch-later shortcut + explicit add-to-playlist). +class VideoRefIn(BaseModel): + video_id: str + + @router.post("") def create_playlist( - payload: dict, + body: CreatePlaylistIn, user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: - name = (payload.get("name") or "").strip() + name = body.name.strip() if not name: raise HTTPException(status_code=400, detail="name is required") pl = Playlist(user_id=user.id, name=name, position=_next_playlist_position(db, user.id)) @@ -233,13 +243,13 @@ def _watch_later(db: Session, user: User) -> Playlist: @router.post("/watch-later") def add_watch_later( - payload: dict, + body: VideoRefIn, user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: """Bookmark shortcut: add a video to the built-in Watch later playlist.""" - video_id = payload.get("video_id") - if not video_id or db.get(Video, video_id) is None: + video_id = body.video_id + if db.get(Video, video_id) is None: raise HTTPException(status_code=404, detail="Unknown video") pl = _watch_later(db, user) _add_item(db, pl, video_id, mark_dirty=False) @@ -394,16 +404,20 @@ def get_playlist( } +class RenamePlaylistIn(BaseModel): + name: str | None = None + + @router.patch("/{playlist_id}") def rename_playlist( playlist_id: int, - payload: dict, + body: RenamePlaylistIn, user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: pl = _own_playlist(db, user, playlist_id) - if "name" in payload: - name = (payload.get("name") or "").strip() + if "name" in body.model_fields_set: + name = (body.name or "").strip() if not name: raise HTTPException(status_code=400, detail="name cannot be empty") if name != pl.name: @@ -462,13 +476,13 @@ def delete_playlist( @router.post("/{playlist_id}/items") def add_item( playlist_id: int, - payload: dict, + body: VideoRefIn, user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: pl = _own_playlist(db, user, playlist_id) - video_id = payload.get("video_id") - if not video_id or db.get(Video, video_id) is None: + video_id = body.video_id + if db.get(Video, video_id) is None: raise HTTPException(status_code=404, detail="Unknown video") _add_item(db, pl, video_id, mark_dirty=True) return {"playlist_id": pl.id, "video_id": video_id} @@ -486,15 +500,19 @@ def remove_item( return {"playlist_id": pl.id, "video_id": video_id} +class ReorderItemsIn(BaseModel): + video_ids: list[str] = [] + + @router.put("/{playlist_id}/order") def reorder_items( playlist_id: int, - payload: dict, + body: ReorderItemsIn, user: User = Depends(current_user), db: Session = Depends(get_db), ) -> dict: pl = _own_playlist(db, user, playlist_id) - order = payload.get("video_ids") or [] + order = body.video_ids items = { it.video_id: it for it in db.execute(