feat: fix auth, provider health checks, search, and redesign UI
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled

- 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
This commit is contained in:
root
2026-03-28 00:14:31 +00:00
parent 5d23a3d663
commit 3dc5dd8fe9
36 changed files with 2735 additions and 1989 deletions
+86 -26
View File
@@ -6,7 +6,15 @@ import re
import json
from typing import List, Optional
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Response, Request, Query
from fastapi import (
APIRouter,
BackgroundTasks,
Depends,
HTTPException,
Response,
Request,
Query,
)
from fastapi.templating import Jinja2Templates
from app.download_manager import DownloadManager
@@ -20,7 +28,7 @@ from app.models.watchlist import (
WatchlistSettings,
WatchlistStatus,
)
from app.routers.router_auth import get_current_user_from_token
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")
@@ -28,6 +36,7 @@ templates = Jinja2Templates(directory="templates")
def get_download_manager() -> DownloadManager:
from main import download_manager
return download_manager
@@ -41,38 +50,56 @@ async def add_to_watchlist(
from main import watchlist_manager
try:
existing = watchlist_manager.get_by_anime_url(item_data.anime_url, current_user.id)
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"
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}})
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("", response_model=List[WatchlistItem])
@router.get("")
async def get_watchlist(
request: Request,
status: Optional[WatchlistStatus] = None,
html: bool = Query(False),
current_user: User = Depends(get_current_user_from_token),
current_user: Optional[User] = Depends(get_optional_user),
):
"""Get user's watchlist (supports HTML for HTMX)"""
from main import watchlist_manager
items = watchlist_manager.get_all(user_id=current_user.id, status=status)
if html or request.headers.get("HX-Request"):
is_htmx = request.headers.get("HX-Request")
if current_user is None and (html or is_htmx):
return templates.TemplateResponse(
"components/watchlist_items_list.html",
{"request": request, "items": items}
"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
@@ -82,6 +109,7 @@ async def get_watchlist_settings(
):
"""Get global watchlist settings"""
from main import watchlist_manager
return watchlist_manager.get_settings()
@@ -97,9 +125,18 @@ async def update_watchlist_settings(
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"}})
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))
@@ -112,6 +149,7 @@ async def get_watchlist_item(
):
"""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")
@@ -133,8 +171,15 @@ async def update_watchlist_item(
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"}})
response.headers["HX-Trigger"] = json.dumps(
{
"show-toast": {
"message": f"'{updated_item.anime_title}' mis à jour",
"type": "success",
}
}
)
return updated_item
@@ -153,10 +198,17 @@ async def delete_from_watchlist(
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"}})
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")
@@ -168,10 +220,17 @@ async def check_watchlist_now(
):
"""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"}})
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"}
@@ -181,4 +240,5 @@ async def get_watchlist_stats(
):
"""Get watchlist statistics for the user"""
from main import watchlist_manager
return watchlist_manager.get_stats(current_user.id)