98 lines
4.7 KiB
Python
98 lines
4.7 KiB
Python
"""Точка входа FastAPI приложения."""
|
||
|
||
import logging
|
||
from collections.abc import AsyncGenerator
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.staticfiles import StaticFiles
|
||
|
||
from api.admin import router as admin_router
|
||
from api.auth import router as auth_router
|
||
from api.chat import router as chat_router
|
||
from api.conferences import router as conferences_router
|
||
from api.health import router as health_router
|
||
from api.livekit_webhook import router as livekit_webhook_router
|
||
from api.metrics import prometheus_latency_middleware
|
||
from api.metrics import router as metrics_router
|
||
from api.teams import router as teams_router
|
||
from api.users import router as users_router
|
||
from core.config import get_settings
|
||
from core.db import async_session_maker, engine
|
||
from core.redis import redis_client
|
||
from services.instance_settings import InstanceSettingsService, bootstrap_overrides_from_settings
|
||
|
||
|
||
def _configure_logging() -> None:
|
||
"""Настроить базовый вывод логов приложения (INFO) в stdout.
|
||
|
||
Без этого root-логгер под uvicorn остаётся без хендлера/на уровне
|
||
WARNING, и INFO-логи наших модулей (например, `services.email` —
|
||
ссылка подтверждения email в dev-режиме) никуда не попадают, включая
|
||
`docker logs`. `logging.basicConfig` идемпотентен: если хендлер на
|
||
root уже есть (в т.ч. при повторных вызовах `create_app()` в тестах),
|
||
он не добавляется повторно — дублирования строк не будет.
|
||
"""
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
||
)
|
||
logging.getLogger().setLevel(logging.INFO)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||
"""Бутстрап настроек инстанса из `plugins.yaml` + освобождение движка БД и Redis при завершении.
|
||
|
||
Бутстрап (`InstanceSettingsService.ensure_bootstrapped`) идемпотентен
|
||
(`INSERT ... ON CONFLICT DO NOTHING`) — безопасен при каждом рестарте
|
||
backend, уже сделанные администратором правки настроек не перетираются.
|
||
`overrides` — матрица «пресет → настройки» инсталлятора
|
||
(`BOOTSTRAP_*` в `.env`): влияет только на чистую БД
|
||
(первый запуск), см. докстринг `ensure_bootstrapped`.
|
||
"""
|
||
settings = get_settings()
|
||
overrides = bootstrap_overrides_from_settings(settings)
|
||
async with async_session_maker() as session:
|
||
await InstanceSettingsService(session).ensure_bootstrapped(
|
||
settings.plugins_config_path, overrides
|
||
)
|
||
try:
|
||
yield
|
||
finally:
|
||
await engine.dispose()
|
||
await redis_client.aclose()
|
||
|
||
|
||
def create_app() -> FastAPI:
|
||
"""Построить и настроить FastAPI приложение."""
|
||
_configure_logging()
|
||
app = FastAPI(title="VidConf API", lifespan=lifespan)
|
||
# Латентность HTTP по маршрутам — оборачивает
|
||
# ВСЕ запросы, включая сам /metrics (шаблон пути резолвится по
|
||
# `request.app.routes` в момент запроса, а не при регистрации — порядок
|
||
# относительно `include_router` ниже значения не имеет).
|
||
app.middleware("http")(prometheus_latency_middleware)
|
||
app.include_router(health_router)
|
||
app.include_router(metrics_router)
|
||
app.include_router(auth_router)
|
||
app.include_router(users_router)
|
||
app.include_router(teams_router)
|
||
app.include_router(conferences_router)
|
||
app.include_router(chat_router)
|
||
app.include_router(admin_router)
|
||
app.include_router(livekit_webhook_router)
|
||
|
||
# Статика загруженных файлов (аватары) — в проде эту же
|
||
# директорию раздаёт nginx (`location /media/`, `deploy/nginx/nginx.conf`),
|
||
# здесь — dev-режим/fallback. Каталог создаётся заранее: `StaticFiles`
|
||
# падает при монтировании несуществующей директории.
|
||
media_root = Path(get_settings().media_root)
|
||
media_root.mkdir(parents=True, exist_ok=True)
|
||
app.mount("/media", StaticFiles(directory=media_root), name="media")
|
||
return app
|
||
|
||
|
||
app = create_app()
|