rebase
This commit is contained in:
123
app/main.py
Normal file
123
app/main.py
Normal file
@@ -0,0 +1,123 @@
|
||||
"""FastAPI app factory and lifespan (DB + Qdrant init, settings seed)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy import select, text
|
||||
|
||||
from app import __version__
|
||||
from app.api import admin, auth, misc, presets, sessions, worlds
|
||||
from app.config import get_settings
|
||||
from app.core.logging import configure_logging, get_logger
|
||||
from app.core.qdrant_client import dispose_qdrant_client, init_qdrant_collections
|
||||
from app.core.settings_service import (
|
||||
get_admin_setup_token,
|
||||
seed_default_settings,
|
||||
)
|
||||
from app.db import dispose_engine, get_sessionmaker
|
||||
from app.models import Setting, User
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
"""Application startup / shutdown lifecycle."""
|
||||
cfg = get_settings()
|
||||
configure_logging()
|
||||
_logger.info("app_starting", version=__version__, debug=cfg.debug)
|
||||
|
||||
# Startup -------------------------------------------------------------
|
||||
sm = get_sessionmaker()
|
||||
try:
|
||||
async with sm() as session:
|
||||
# 1) seed settings (idempotent)
|
||||
await seed_default_settings(session)
|
||||
# 2) ensure admin setup token
|
||||
token = await get_admin_setup_token(session)
|
||||
# 3) check if any admin exists
|
||||
from sqlalchemy import func
|
||||
|
||||
admins_count = (
|
||||
await session.execute(
|
||||
select(func.count(User.id)).where(User.is_admin.is_(True))
|
||||
)
|
||||
).scalar_one()
|
||||
if admins_count == 0:
|
||||
_logger.warning(
|
||||
"no_admin_yet",
|
||||
setup_url=f"/register/admin?token={token}",
|
||||
)
|
||||
print(f"\n=== AI-RPG Admin Setup ===")
|
||||
print(f"No admin user yet. Open this URL in your browser:")
|
||||
print(f" /register/admin?token={token}")
|
||||
print(f"===========================\n")
|
||||
else:
|
||||
_logger.info("admins_present", count=admins_count)
|
||||
# 4) Init Qdrant collections
|
||||
from app.core.settings_service import get_setting
|
||||
|
||||
dim = int(await get_setting(session, "embeddings.dimension") or 256)
|
||||
try:
|
||||
await init_qdrant_collections(dim)
|
||||
except Exception as e: # noqa: BLE001
|
||||
_logger.warning("qdrant_init_failed", error=str(e))
|
||||
except Exception as e: # noqa: BLE001
|
||||
_logger.error("startup_failed", error=str(e))
|
||||
# Don't crash — let /api/health reflect the broken state
|
||||
pass
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown ------------------------------------------------------------
|
||||
_logger.info("app_stopping")
|
||||
await dispose_qdrant_client()
|
||||
await dispose_engine()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Build and return the FastAPI application."""
|
||||
cfg = get_settings()
|
||||
app = FastAPI(
|
||||
title=cfg.app_name,
|
||||
version=__version__,
|
||||
description="AI-RPG — text RPG with an LLM Game Master.",
|
||||
lifespan=lifespan,
|
||||
docs_url="/api/docs",
|
||||
redoc_url=None,
|
||||
openapi_url="/api/openapi.json",
|
||||
)
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=cfg.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Static asset serving (icons / uploads)
|
||||
assets_dir = Path(cfg.assets_dir)
|
||||
assets_dir.mkdir(parents=True, exist_ok=True)
|
||||
app.mount("/static/assets", StaticFiles(directory=str(assets_dir)), name="assets")
|
||||
|
||||
# Routers
|
||||
app.include_router(misc.router)
|
||||
app.include_router(auth.router)
|
||||
app.include_router(worlds.router)
|
||||
app.include_router(sessions.router)
|
||||
app.include_router(presets.router)
|
||||
app.include_router(admin.router)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
Reference in New Issue
Block a user