3dc5dd8fe9
- Fix register/login: dict-style access on UserTable ORM objects - Fix HTMX auth: inject JWT token in all HTMX request headers - Fix FS7 search: use DLE AJAX endpoint /engine/ajax/search.php - Fix ZT search: use ?p=series&search=QUERY (not DLE format) - Fix provider health: load hardcoded providers + domain manager - Add self.id to all anime/series providers - Redesign homepage: Netflix-style horizontal scroll cards (.hc) - Redesign search results: grouped by title, poster + synopsis + 3 buttons - Add Télécharger dropdown: season download + episode picker - Fix navbar CSS: restore .tabs flex layout, remove orphan rules - Fix HTMX spinner: remove inline display:none, use CSS indicator - Add AGENTS.md files across project for developer documentation
245 lines
7.1 KiB
Python
245 lines
7.1 KiB
Python
"""
|
|
Watchlist management routes for Ohm Stream Downloader API.
|
|
"""
|
|
|
|
import re
|
|
import json
|
|
from typing import List, Optional
|
|
|
|
from fastapi import (
|
|
APIRouter,
|
|
BackgroundTasks,
|
|
Depends,
|
|
HTTPException,
|
|
Response,
|
|
Request,
|
|
Query,
|
|
)
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
from app.download_manager import DownloadManager
|
|
from app.downloaders import get_downloader
|
|
from app.models import DownloadRequest
|
|
from app.models.auth import User
|
|
from app.models.watchlist import (
|
|
WatchlistItem,
|
|
WatchlistItemCreate,
|
|
WatchlistItemUpdate,
|
|
WatchlistSettings,
|
|
WatchlistStatus,
|
|
)
|
|
from app.routers.router_auth import get_current_user_from_token, get_optional_user
|
|
|
|
router = APIRouter(prefix="/api/watchlist", tags=["watchlist"])
|
|
templates = Jinja2Templates(directory="templates")
|
|
|
|
|
|
def get_download_manager() -> DownloadManager:
|
|
from main import download_manager
|
|
|
|
return download_manager
|
|
|
|
|
|
@router.post("", response_model=WatchlistItem)
|
|
async def add_to_watchlist(
|
|
item_data: WatchlistItemCreate,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Add an anime to the watchlist"""
|
|
from main import watchlist_manager
|
|
|
|
try:
|
|
existing = watchlist_manager.get_by_anime_url(
|
|
item_data.anime_url, current_user.id
|
|
)
|
|
item = watchlist_manager.add(current_user.id, item_data)
|
|
|
|
msg = (
|
|
f"'{item.anime_title}' ajouté à la watchlist"
|
|
if not existing
|
|
else f"'{item.anime_title}' est déjà dans la watchlist"
|
|
)
|
|
toast_type = "success" if not existing else "info"
|
|
response.headers["HX-Trigger"] = json.dumps(
|
|
{"show-toast": {"message": msg, "type": toast_type}}
|
|
)
|
|
|
|
return item
|
|
except Exception as e:
|
|
from logging import getLogger
|
|
|
|
logger = getLogger(__name__)
|
|
logger.error(f"Error adding to watchlist: {e}", exc_info=True)
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("")
|
|
async def get_watchlist(
|
|
request: Request,
|
|
status: Optional[WatchlistStatus] = None,
|
|
html: bool = Query(False),
|
|
current_user: Optional[User] = Depends(get_optional_user),
|
|
):
|
|
from main import watchlist_manager
|
|
|
|
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")
|
|
|
|
items = watchlist_manager.get_all(user_id=current_user.id, status=status)
|
|
|
|
if html or is_htmx:
|
|
return templates.TemplateResponse(
|
|
"components/watchlist_items_list.html", {"request": request, "items": items}
|
|
)
|
|
|
|
return items
|
|
|
|
|
|
@router.get("/settings", response_model=WatchlistSettings)
|
|
async def get_watchlist_settings(
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get global watchlist settings"""
|
|
from main import watchlist_manager
|
|
|
|
return watchlist_manager.get_settings()
|
|
|
|
|
|
@router.put("/settings", response_model=WatchlistSettings)
|
|
async def update_watchlist_settings(
|
|
settings: WatchlistSettings,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Update global watchlist settings"""
|
|
from main import auto_download_scheduler, watchlist_manager
|
|
|
|
try:
|
|
updated_settings = watchlist_manager.update_settings(settings)
|
|
if auto_download_scheduler.is_running():
|
|
auto_download_scheduler.update_interval(
|
|
updated_settings.check_interval_hours
|
|
)
|
|
|
|
response.headers["HX-Trigger"] = json.dumps(
|
|
{
|
|
"show-toast": {
|
|
"message": "Paramètres de la watchlist mis à jour",
|
|
"type": "success",
|
|
}
|
|
}
|
|
)
|
|
return updated_settings
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
@router.get("/{item_id}", response_model=WatchlistItem)
|
|
async def get_watchlist_item(
|
|
item_id: str,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get a specific watchlist item"""
|
|
from main import watchlist_manager
|
|
|
|
item = watchlist_manager.get_by_id(item_id)
|
|
if not item or item.user_id != current_user.id:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
return item
|
|
|
|
|
|
@router.put("/{item_id}", response_model=WatchlistItem)
|
|
async def update_watchlist_item(
|
|
item_id: str,
|
|
update_data: WatchlistItemUpdate,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Update a watchlist item"""
|
|
from main import watchlist_manager
|
|
|
|
item = watchlist_manager.get_by_id(item_id)
|
|
if not item or item.user_id != current_user.id:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
updated_item = watchlist_manager.update(item_id, update_data)
|
|
|
|
response.headers["HX-Trigger"] = json.dumps(
|
|
{
|
|
"show-toast": {
|
|
"message": f"'{updated_item.anime_title}' mis à jour",
|
|
"type": "success",
|
|
}
|
|
}
|
|
)
|
|
return updated_item
|
|
|
|
|
|
@router.delete("/{item_id}")
|
|
async def delete_from_watchlist(
|
|
item_id: str,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Remove an anime from the watchlist"""
|
|
from main import watchlist_manager
|
|
|
|
item = watchlist_manager.get_by_id(item_id)
|
|
if not item or item.user_id != current_user.id:
|
|
raise HTTPException(status_code=404, detail="Item not found")
|
|
|
|
title = item.anime_title
|
|
if watchlist_manager.delete(item_id):
|
|
response.headers["HX-Trigger"] = json.dumps(
|
|
{
|
|
"show-toast": {
|
|
"message": f"'{title}' supprimé de la watchlist",
|
|
"type": "info",
|
|
}
|
|
}
|
|
)
|
|
# HTMX will handle removing the element if target is specified in the frontend
|
|
return Response(status_code=204)
|
|
|
|
raise HTTPException(status_code=500, detail="Failed to delete item")
|
|
|
|
|
|
@router.post("/check", response_model=List)
|
|
async def check_watchlist_now(
|
|
background_tasks: BackgroundTasks,
|
|
response: Response,
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Trigger an immediate check for new episodes"""
|
|
from main import auto_download_scheduler
|
|
|
|
background_tasks.add_task(auto_download_scheduler.trigger_check_now)
|
|
response.headers["HX-Trigger"] = json.dumps(
|
|
{
|
|
"show-toast": {
|
|
"message": "Vérification de la watchlist lancée en arrière-plan",
|
|
"type": "info",
|
|
}
|
|
}
|
|
)
|
|
|
|
return {"status": "success", "message": "Check triggered"}
|
|
|
|
|
|
@router.get("/stats/summary")
|
|
async def get_watchlist_stats(
|
|
current_user: User = Depends(get_current_user_from_token),
|
|
):
|
|
"""Get watchlist statistics for the user"""
|
|
from main import watchlist_manager
|
|
|
|
return watchlist_manager.get_stats(current_user.id)
|