myYouTube/backend/app/main.py

101 lines
3.1 KiB
Python
Raw Permalink Normal View History

import asyncio
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.db import SessionLocal
from app.services import download_jobs
from app.services.metube_client import MeTubeClient
from app.services.scheduler import create_scheduler
logging.basicConfig(level=settings.log_level)
logger = logging.getLogger(__name__)
async def _on_metube_event(event_name: str, payload) -> None:
db = SessionLocal()
try:
await download_jobs.handle_metube_event(db, event_name, payload)
except Exception:
logger.exception("Failed handling MeTube '%s' event", event_name)
finally:
db.close()
def _reconcile_download_jobs() -> None:
db = SessionLocal()
try:
download_jobs.reconcile_on_startup(db)
except Exception:
logger.exception("Failed to reconcile download jobs on startup")
finally:
db.close()
@asynccontextmanager
async def lifespan(_: FastAPI):
logger.info("Application startup")
_reconcile_download_jobs()
scheduler = create_scheduler()
scheduler.start()
metube_task = asyncio.create_task(MeTubeClient().run_event_listener(_on_metube_event))
yield
metube_task.cancel()
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")