99 lines
3.8 KiB
Python
99 lines
3.8 KiB
Python
"""Создание FastAPI-приложения админки."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import secrets
|
||
from collections.abc import AsyncIterator
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
|
||
from fastapi import FastAPI
|
||
from fastapi.templating import Jinja2Templates
|
||
from starlette.middleware.sessions import SessionMiddleware
|
||
|
||
from admin.media import ensure_media
|
||
from admin.middleware import AdminAuthMiddleware
|
||
from admin.routes import admins, auth, health, logs, messages, pages, schedules, sequences, users
|
||
from common import __version__
|
||
from common.analytics import read as analytics_read
|
||
from common.config import AdminSettings, get_admin_settings
|
||
from common.db.pool import close_pool, init_pool
|
||
from common.logging_config import setup_logging
|
||
from common.repositories import admins_repo
|
||
|
||
BASE_DIR = Path(__file__).resolve().parent
|
||
TEMPLATES_DIR = BASE_DIR / "templates"
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def create_app(settings: AdminSettings | None = None, *, init_db: bool = True) -> FastAPI:
|
||
"""Собрать приложение: настройки, шаблоны, роутеры.
|
||
|
||
Настройки можно передать явно (тесты); по умолчанию читаются из окружения.
|
||
``init_db=False`` — не открывать пул Postgres (юнит-тесты без БД).
|
||
"""
|
||
setup_logging()
|
||
settings = settings or get_admin_settings()
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||
if init_db:
|
||
await init_pool(app.state.settings)
|
||
ensure_media(app.state.settings)
|
||
try:
|
||
inserted = await admins_repo.ensure_admins(app.state.settings.admin_ids)
|
||
if inserted:
|
||
logger.info("Добавлены администраторы из ADMIN_IDS: %s", inserted)
|
||
except Exception:
|
||
logger.warning(
|
||
"Не удалось засеять администраторов (применены ли миграции?)",
|
||
exc_info=True,
|
||
)
|
||
try:
|
||
yield
|
||
finally:
|
||
if init_db:
|
||
await close_pool()
|
||
await analytics_read.close_client()
|
||
|
||
app = FastAPI(
|
||
title="DispelEvilBot Admin",
|
||
version=__version__,
|
||
# Swagger/OpenAPI наружу пока не нужны: админка закрыта логином (Блок 4.2).
|
||
docs_url=None,
|
||
redoc_url=None,
|
||
openapi_url=None,
|
||
lifespan=lifespan,
|
||
)
|
||
app.state.settings = settings
|
||
app.state.templates = Jinja2Templates(directory=str(TEMPLATES_DIR))
|
||
|
||
# Порядок важен: SessionMiddleware добавляется последним и оборачивает auth,
|
||
# поэтому request.session доступен в проверке администратора.
|
||
app.add_middleware(AdminAuthMiddleware)
|
||
session_secret = settings.admin_session_secret
|
||
if not session_secret:
|
||
session_secret = secrets.token_urlsafe(32)
|
||
logger.warning("ADMIN_SESSION_SECRET не задан — сессии слетят при перезапуске")
|
||
app.add_middleware(
|
||
SessionMiddleware,
|
||
secret_key=session_secret,
|
||
session_cookie="debot_admin",
|
||
max_age=settings.admin_session_max_age,
|
||
same_site="lax",
|
||
https_only=settings.is_prod,
|
||
)
|
||
|
||
app.include_router(health.router)
|
||
app.include_router(auth.router)
|
||
app.include_router(pages.router)
|
||
app.include_router(messages.router)
|
||
app.include_router(sequences.router)
|
||
app.include_router(schedules.router)
|
||
app.include_router(users.router)
|
||
app.include_router(admins.router)
|
||
app.include_router(logs.router)
|
||
return app
|