import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI from fastapi.staticfiles import StaticFiles from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.middleware.sessions import SessionMiddleware from starlette.types import Scope class SPAStaticFiles(StaticFiles): """Falls back to index.html for unknown paths so client-side routes (e.g. /categories) work on direct navigation/refresh, not just /.""" async def get_response(self, path: str, scope: Scope): try: return await super().get_response(path, scope) except StarletteHTTPException as exc: if exc.status_code == 404: return await super().get_response("index.html", scope) raise from app.api.auth import router as auth_router from app.api.categories import router as categories_router from app.api.channels import router as channels_router from app.api.feed import router as feed_router from app.api.health import router as health_router from app.api.sync import router as sync_router from app.api.videos import router as videos_router from app.config import settings from app.services.scheduler import create_scheduler logging.basicConfig(level=settings.log_level) logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(_: FastAPI): logger.info("Application startup") scheduler = create_scheduler() scheduler.start() yield scheduler.shutdown(wait=False) logger.info("Application shutdown") app = FastAPI(title="MyYouTube", lifespan=lifespan) app.add_middleware( SessionMiddleware, secret_key=settings.app_secret_key, session_cookie="myyoutube_session", same_site="lax", https_only=settings.app_base_url.startswith("https"), max_age=60 * 60 * 24 * 30, ) app.include_router(health_router, prefix="/api") app.include_router(auth_router, prefix="/api") app.include_router(sync_router, prefix="/api") app.include_router(channels_router, prefix="/api") app.include_router(categories_router, prefix="/api") app.include_router(feed_router, prefix="/api") app.include_router(videos_router, prefix="/api") STATIC_DIR = Path(__file__).resolve().parent.parent / "static" if STATIC_DIR.is_dir(): app.mount("/", SPAStaticFiles(directory=STATIC_DIR, html=True), name="static")