"""Создание 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.middleware import AdminAuthMiddleware from admin.routes import auth, health, messages, pages, schedules from common import __version__ 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) 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() 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(schedules.router) return app