105 lines
2.7 KiB
Python
105 lines
2.7 KiB
Python
import asyncio
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.responses import JSONResponse
|
|
from loguru import logger
|
|
|
|
from app.config import setup_logging
|
|
from app.database import engine
|
|
from app.settings import settings
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
setup_logging()
|
|
logger.info("Starting {} v{}", settings.app_name, "0.1.0")
|
|
|
|
async with engine.connect() as conn:
|
|
await conn.exec_driver_sql("SELECT 1")
|
|
logger.info("Database connection established")
|
|
|
|
from app.bot.dispatcher import create_bot, create_dispatcher
|
|
from app.scheduler import setup_scheduler
|
|
|
|
bot = create_bot()
|
|
dp = create_dispatcher()
|
|
polling_task = asyncio.create_task(dp.start_polling(bot))
|
|
logger.info("Telegram bot started")
|
|
|
|
scheduler = setup_scheduler()
|
|
scheduler.start()
|
|
logger.info("Scheduler started")
|
|
|
|
yield
|
|
|
|
scheduler.shutdown(wait=True)
|
|
logger.info("Scheduler stopped")
|
|
polling_task.cancel()
|
|
try:
|
|
await polling_task
|
|
except asyncio.CancelledError:
|
|
pass
|
|
await bot.session.close()
|
|
logger.info("Telegram bot stopped")
|
|
await engine.dispose()
|
|
logger.info("Database engine disposed")
|
|
|
|
|
|
app = FastAPI(
|
|
title=settings.app_name,
|
|
version="0.1.0",
|
|
debug=settings.debug,
|
|
lifespan=lifespan,
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
)
|
|
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_exception_handler(
|
|
request: Request, exc: RequestValidationError
|
|
):
|
|
logger.warning("Validation error: {}", exc.errors())
|
|
body = exc.body
|
|
if body is not None and not isinstance(body, (dict, list, str, int, float, bool, type(None))):
|
|
body = str(body)
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content={
|
|
"detail": exc.errors(),
|
|
"body": body,
|
|
},
|
|
)
|
|
|
|
|
|
@app.exception_handler(Exception)
|
|
async def global_exception_handler(request: Request, exc: Exception):
|
|
logger.error("Unhandled error: {}", exc)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"detail": "Internal server error"},
|
|
)
|
|
|
|
|
|
from app.admin.router import router as admin_router
|
|
from app.api.v1.router import router as v1_router
|
|
|
|
app.include_router(admin_router)
|
|
app.include_router(v1_router)
|
|
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
try:
|
|
async with engine.connect() as conn:
|
|
await conn.exec_driver_sql("SELECT 1")
|
|
return {"status": "ok", "database": "connected"}
|
|
except Exception as e:
|
|
logger.error("Health check failed: {}", e)
|
|
return JSONResponse(
|
|
status_code=503,
|
|
content={"status": "error", "database": str(e)},
|
|
)
|