- Rails Nouveautés séparés : 🆕 animés (tri date Kitsu) et 🆕 séries & films (French-Stream) — chaque type garde son rail, plus d'écrasement mutuel - Section 🎭 Explorer : chips type × genre — animés via catégories Kitsu, séries (/<genre>-series-/) et films (/films/<genre>/) via French-Stream ; état dans l'URL (/discover?t=serie&g=medical), partageable - Scraper : browse(media_type, genre) + catalogue YAML surchargeable ; parsing div.short mutualisé avec les nouveautés - Films réels retirés du mode « Animés » (ils suivent le rail séries) - Endpoints /api/discover (latest_anime/latest_serie), /api/discover/genres, /api/discover/browse — parcours filtré par la préférence du compte - Warmup démarrage : latest_by_type()
85 lines
2.2 KiB
Python
85 lines
2.2 KiB
Python
import asyncio
|
|
import logging
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
|
from app.config import BASE_DIR, get_settings
|
|
from app.db import db
|
|
from app.logging_config import setup_logging
|
|
from app.routers import (
|
|
admin,
|
|
auth,
|
|
discover,
|
|
downloads,
|
|
library,
|
|
pages,
|
|
proxy,
|
|
qbit,
|
|
search,
|
|
system,
|
|
torznab,
|
|
)
|
|
from app.scrapers.http import close_client
|
|
from app.services.discover import discover as discover_service
|
|
from app.services.downloads import download_manager
|
|
from app.services.settings import apply_source_base_urls
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _log_task_error(task: asyncio.Task) -> None:
|
|
if task.cancelled():
|
|
return
|
|
exc = task.exception()
|
|
if exc is not None:
|
|
logger.warning("Réchauffe découverte échouée : %s", exc)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
settings = get_settings()
|
|
setup_logging(settings.debug)
|
|
await db.connect()
|
|
await apply_source_base_urls()
|
|
await download_manager.start()
|
|
warmup = asyncio.create_task(discover_service.latest_by_type())
|
|
warmup.add_done_callback(_log_task_error)
|
|
logger.info("%s prêt", settings.app_name)
|
|
yield
|
|
warmup.cancel()
|
|
await download_manager.stop()
|
|
await close_client()
|
|
await db.close()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
settings = get_settings()
|
|
app = FastAPI(title=settings.app_name, lifespan=lifespan)
|
|
app.include_router(torznab.router)
|
|
app.include_router(qbit.router)
|
|
app.include_router(system.router)
|
|
app.include_router(pages.router)
|
|
app.include_router(pages.protected)
|
|
|
|
app.include_router(auth.router)
|
|
app.include_router(search.router)
|
|
app.include_router(discover.router)
|
|
app.include_router(downloads.router)
|
|
app.include_router(library.router)
|
|
app.include_router(admin.router)
|
|
app.include_router(proxy.router)
|
|
|
|
app.mount("/static", StaticFiles(directory=BASE_DIR / "app" / "static"), name="static")
|
|
|
|
@app.get("/health")
|
|
async def health() -> dict[str, str]:
|
|
return {"status": "ok"}
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|