278 lines
8.7 KiB
Python
278 lines
8.7 KiB
Python
"""
|
|
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)}") |