import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from sqlalchemy import select # uvicorn's dictConfig only configures uvicorn.* loggers; the root logger # ends up with no handler, so app.* records are silently discarded. # Give the app namespace its own StreamHandler to guarantee output. _app_logger = logging.getLogger("app") _app_logger.setLevel(logging.INFO) if not _app_logger.handlers: _h = logging.StreamHandler() _h.setFormatter(logging.Formatter("%(levelname)-8s [%(name)s] %(message)s")) _app_logger.addHandler(_h) _app_logger.propagate = False log = logging.getLogger(__name__) from app.config import settings from app.database import engine, Base, SessionLocal from app.routers import libraries, media, tags, search, auth from app.services import watcher as watcher_service from app.auth import hash_password import app.models # noqa: F401 — registers models with Base.metadata async def _ensure_admin_user(): from app.models import User if not settings.admin_password: log.warning("ADMIN_PASSWORD not set — no admin user will be created.") return async with SessionLocal() as db: result = await db.execute(select(User).where(User.username == settings.admin_username)) if result.scalars().first(): log.info("Admin user '%s' already exists.", settings.admin_username) return user = User( username=settings.admin_username, password_hash=hash_password(settings.admin_password), is_admin=True, ) db.add(user) await db.commit() log.info("Created admin user '%s'.", settings.admin_username) @asynccontextmanager async def lifespan(app: FastAPI): log.info("Creating database tables...") async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) await _ensure_admin_user() log.info("Starting library watchers...") await watcher_service.start_all() log.info("Startup complete.") yield await watcher_service.stop_all() app = FastAPI(title="MediaLore", lifespan=lifespan) @app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception): log.exception("Unhandled error on %s %s", request.method, request.url) return JSONResponse(status_code=500, content={"detail": str(exc)}) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) app.include_router(auth.router, prefix="/api") app.include_router(libraries.router, prefix="/api") app.include_router(media.router, prefix="/api") app.include_router(tags.router, prefix="/api") app.include_router(search.router, prefix="/api")