5d264d8f3b
- router_favorites.py: toutes les routes requièrent maintenant l'auth - GET utilise get_optional_user + login_prompt.html pour HTMX - POST/DELETE/toggle requièrent get_current_user_from_token - Filtrage par user_id dans toutes les requêtes favorites - router_downloads.py: GET list et GET status protégés (401 sans token) - router_recommendations.py: GET protégé (login_prompt HTMX, 401 JSON) - router_sonarr.py: tous les endpoints de gestion protégés - Webhooks restent publics (reçus de Sonarr) - app/favorites.py: ajout du paramètre user_id à toutes les méthodes Closes #15
164 lines
5.1 KiB
Python
164 lines
5.1 KiB
Python
"""
|
|
Favorites management routes for Ohm Stream Downloader API.
|
|
"""
|
|
|
|
from typing import Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Response
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.favorites import get_favorites_manager
|
|
from app.models.auth import User
|
|
from app.routers.router_auth import get_current_user_from_token, get_optional_user
|
|
|
|
router = APIRouter(prefix="/api/favorites", tags=["favorites"])
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
@router.get("")
|
|
async def list_favorites(
|
|
request: Request,
|
|
sort_by: str = "created_at",
|
|
order: str = "desc",
|
|
filter_provider: Optional[str] = None,
|
|
filter_genre: Optional[str] = None,
|
|
html: bool = Query(False),
|
|
current_user: Optional[User] = Depends(get_optional_user),
|
|
):
|
|
"""List all favorite anime with optional sorting and filtering"""
|
|
is_htmx = request.headers.get("HX-Request")
|
|
|
|
if current_user is None and (html or is_htmx):
|
|
return templates.TemplateResponse(
|
|
"components/login_prompt.html", {"request": request}
|
|
)
|
|
|
|
if current_user is None:
|
|
raise HTTPException(status_code=401, detail="Authentication required")
|
|
|
|
fav_manager = get_favorites_manager()
|
|
favorites = await fav_manager.list_favorites(
|
|
user_id=current_user.id,
|
|
sort_by=sort_by,
|
|
order=order,
|
|
filter_provider=filter_provider,
|
|
filter_genre=filter_genre,
|
|
)
|
|
return {
|
|
"favorites": favorites,
|
|
"total": len(favorites),
|
|
"filters": {
|
|
"sort_by": sort_by,
|
|
"order": order,
|
|
"provider": filter_provider,
|
|
"genre": filter_genre,
|
|
},
|
|
}
|
|
|
|
|
|
@router.post("")
|
|
async def add_favorite(
|
|
request: Request,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Add an anime to favorites"""
|
|
data = await request.json()
|
|
|
|
required_fields = ["anime_id", "title", "url", "provider"]
|
|
for field in required_fields:
|
|
if field not in data:
|
|
raise HTTPException(
|
|
status_code=400, detail=f"Missing required field: {field}"
|
|
)
|
|
|
|
fav_manager = get_favorites_manager()
|
|
favorite = await fav_manager.add_favorite(
|
|
user_id=current_user.id,
|
|
anime_id=data["anime_id"],
|
|
title=data["title"],
|
|
url=data["url"],
|
|
provider=data["provider"],
|
|
metadata=data.get("metadata"),
|
|
poster_url=data.get("poster_url"),
|
|
)
|
|
|
|
response.headers["HX-Trigger"] = '{"show-toast": {"message": "' + favorite['title'] + ' ajouté aux favoris", "type": "success"}}'
|
|
return {"status": "added", "favorite": favorite}
|
|
|
|
|
|
@router.delete("/{anime_id}")
|
|
async def remove_favorite(
|
|
anime_id: str,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Remove an anime from favorites"""
|
|
fav_manager = get_favorites_manager()
|
|
removed = await fav_manager.remove_favorite(anime_id, user_id=current_user.id)
|
|
|
|
if not removed:
|
|
raise HTTPException(status_code=404, detail="Favorite not found")
|
|
|
|
response.headers["HX-Trigger"] = '{"show-toast": {"message": "Favori supprimé", "type": "info"}}'
|
|
return {"status": "removed", "anime_id": anime_id}
|
|
|
|
|
|
@router.get("/stats")
|
|
async def get_favorites_stats(
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get statistics about favorites"""
|
|
fav_manager = get_favorites_manager()
|
|
stats = await fav_manager.get_stats(user_id=current_user.id)
|
|
return stats
|
|
|
|
|
|
@router.get("/{anime_id}")
|
|
async def get_favorite(
|
|
anime_id: str,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get details of a specific favorite anime"""
|
|
fav_manager = get_favorites_manager()
|
|
favorite = await fav_manager.get_favorite(anime_id, user_id=current_user.id)
|
|
|
|
if not favorite:
|
|
raise HTTPException(status_code=404, detail="Favorite not found")
|
|
|
|
return {"favorite": favorite}
|
|
|
|
|
|
@router.post("/toggle")
|
|
async def toggle_favorite(
|
|
request: Request,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Toggle an anime in favorites"""
|
|
data = await request.json()
|
|
|
|
required_fields = ["anime_id", "title", "url", "provider"]
|
|
for field in required_fields:
|
|
if field not in data:
|
|
raise HTTPException(
|
|
status_code=400, detail=f"Missing required field: {field}"
|
|
)
|
|
|
|
fav_manager = get_favorites_manager()
|
|
result = await fav_manager.toggle_favorite(
|
|
user_id=current_user.id,
|
|
anime_id=data["anime_id"],
|
|
title=data["title"],
|
|
url=data["url"],
|
|
provider=data["provider"],
|
|
metadata=data.get("metadata"),
|
|
poster_url=data.get("poster_url"),
|
|
)
|
|
|
|
action = result.get("action", "unknown")
|
|
message = f"'{data['title']}' {'ajouté aux' if action == 'added' else 'retiré des'} favoris"
|
|
toast_type = "success" if action == "added" else "info"
|
|
|
|
response.headers["HX-Trigger"] = f'{{"show-toast": {{"message": "{message}", "type": "{toast_type}"}}}}'
|
|
return result
|