""" Playlist API endpoints """ import logging from typing import List, Optional from datetime import datetime from fastapi import APIRouter, Depends, HTTPException, Query, status from pydantic import BaseModel, HttpUrl from sqlalchemy.orm import Session from ..core.database import get_db from ..core.config import settings from ..models.playlist import PlaylistSubscription from ..services.playlist_service import PlaylistService logger = logging.getLogger(__name__) router = APIRouter() # Pydantic models for API requests/responses class PlaylistCreate(BaseModel): """Playlist creation request model""" url: HttpUrl check_interval: int = settings.DEFAULT_CHECK_INTERVAL start_point: Optional[str] = None # video_id or index quality: str = settings.DEFAULT_QUALITY format: str = settings.DEFAULT_FORMAT folder: Optional[str] = None enabled: bool = True class PlaylistUpdate(BaseModel): """Playlist update request model""" check_interval: Optional[int] = None start_point: Optional[str] = None quality: Optional[str] = None format: Optional[str] = None folder: Optional[str] = None enabled: Optional[bool] = None class PlaylistResponse(BaseModel): """Playlist response model""" id: str url: str title: Optional[str] check_interval: int last_checked: Optional[datetime] start_point: Optional[str] quality: str format: str folder: Optional[str] enabled: bool created_at: datetime updated_at: datetime class Config: from_attributes = True class PlaylistWithStats(PlaylistResponse): """Playlist response with statistics""" stats: dict videos: List[dict] = [] class PlaylistStats(BaseModel): """Playlist statistics""" total: int pending: int downloading: int completed: int failed: int skipped: int @router.get("/", response_model=List[PlaylistResponse]) async def list_playlists( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=1000), enabled: Optional[bool] = None, db: Session = Depends(get_db) ): """List all playlists""" try: service = PlaylistService(db) playlists = service.get_playlists(skip=skip, limit=limit, enabled=enabled) return playlists except Exception as e: logger.error(f"Error listing playlists: {e}") raise HTTPException(status_code=500, detail=f"Error listing playlists: {str(e)}") @router.post("/", response_model=PlaylistResponse, status_code=status.HTTP_201_CREATED) async def create_playlist( playlist: PlaylistCreate, db: Session = Depends(get_db) ): """Add a new playlist for monitoring""" try: service = PlaylistService(db) new_playlist = await service.add_playlist( url=str(playlist.url), check_interval=playlist.check_interval, start_point=playlist.start_point, quality=playlist.quality, format=playlist.format, folder=playlist.folder, enabled=playlist.enabled ) return new_playlist except ValueError as e: logger.warning(f"Invalid playlist URL: {e}") raise HTTPException(status_code=400, detail=f"Invalid playlist URL: {str(e)}") except Exception as e: logger.error(f"Error creating playlist: {e}") raise HTTPException(status_code=500, detail=f"Error creating playlist: {str(e)}") @router.get("/{playlist_id}", response_model=PlaylistWithStats) async def get_playlist( playlist_id: str, include_videos: bool = Query(True), video_status: Optional[str] = Query(None), video_limit: int = Query(50, ge=1, le=500), db: Session = Depends(get_db) ): """Get a specific playlist with details and statistics""" try: service = PlaylistService(db) playlist = service.get_playlist(playlist_id) if not playlist: raise HTTPException(status_code=404, detail="Playlist not found") # Get statistics stats = service.get_playlist_stats(playlist_id) # Get videos if requested videos = [] if include_videos: videos = service.get_playlist_videos( playlist_id=playlist_id, status=video_status, limit=video_limit ) return PlaylistWithStats( **playlist.__dict__, stats=stats, videos=videos ) except HTTPException: raise except Exception as e: logger.error(f"Error getting playlist {playlist_id}: {e}") raise HTTPException(status_code=500, detail=f"Error getting playlist: {str(e)}") @router.put("/{playlist_id}", response_model=PlaylistResponse) async def update_playlist( playlist_id: str, playlist_update: PlaylistUpdate, db: Session = Depends(get_db) ): """Update a playlist""" try: service = PlaylistService(db) # Get existing playlist existing = service.get_playlist(playlist_id) if not existing: raise HTTPException(status_code=404, detail="Playlist not found") # Update playlist updated_playlist = service.update_playlist( playlist_id=playlist_id, **playlist_update.dict(exclude_unset=True) ) return updated_playlist except HTTPException: raise except Exception as e: logger.error(f"Error updating playlist {playlist_id}: {e}") raise HTTPException(status_code=500, detail=f"Error updating playlist: {str(e)}") @router.delete("/{playlist_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_playlist( playlist_id: str, delete_videos: bool = Query(False, description="Also delete all associated video records"), db: Session = Depends(get_db) ): """Delete a playlist""" try: service = PlaylistService(db) # Check if playlist exists existing = service.get_playlist(playlist_id) if not existing: raise HTTPException(status_code=404, detail="Playlist not found") # Delete playlist service.delete_playlist(playlist_id, delete_videos=delete_videos) logger.info(f"Deleted playlist {playlist_id}") except HTTPException: raise except Exception as e: logger.error(f"Error deleting playlist {playlist_id}: {e}") raise HTTPException(status_code=500, detail=f"Error deleting playlist: {str(e)}") @router.post("/{playlist_id}/check", response_model=dict) async def trigger_playlist_check( playlist_id: str, force: bool = Query(False, description="Force check even if recently checked"), db: Session = Depends(get_db) ): """Manually trigger a playlist check""" try: service = PlaylistService(db) # Check if playlist exists existing = service.get_playlist(playlist_id) if not existing: raise HTTPException(status_code=404, detail="Playlist not found") # Trigger check new_videos = await service.check_playlist(playlist_id, force=force) return { "status": "ok", "new_videos": new_videos, "message": f"Playlist check completed. Found {new_videos} new videos." } except HTTPException: raise except Exception as e: logger.error(f"Error checking playlist {playlist_id}: {e}") raise HTTPException(status_code=500, detail=f"Error checking playlist: {str(e)}") @router.post("/{playlist_id}/start-point", response_model=dict) async def update_start_point( playlist_id: str, video_id: str, db: Session = Depends(get_db) ): """Update the start point for a playlist""" try: service = PlaylistService(db) # Check if playlist exists existing = service.get_playlist(playlist_id) if not existing: raise HTTPException(status_code=404, detail="Playlist not found") # Update start point updated_count = service.update_start_point(playlist_id, video_id) return { "status": "ok", "updated_videos": updated_count, "message": f"Updated start point and marked {updated_count} videos as skipped." } except HTTPException: raise except Exception as e: logger.error(f"Error updating start point for playlist {playlist_id}: {e}") raise HTTPException(status_code=500, detail=f"Error updating start point: {str(e)}")