Files
vpn-control-panel/app/main.py
smolkik-code 7c7c88621d Initial commit: VPN Control Panel
- FastAPI REST API with JWT auth
- aiogram 3 Telegram bot with admin middleware
- APScheduler daily tasks (expiry, reminders, revoke, sync)
- SQLAlchemy 2 async ORM with Alembic migrations
- Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs)
- VPN provider abstraction with MockProvider
- Stats service with revenue/subscription analytics
- Docker Compose (PostgreSQL + Redis + app)
- Healthcheck endpoint
2026-07-05 17:50:11 +07:00

102 lines
2.6 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())
return JSONResponse(
status_code=422,
content={
"detail": exc.errors(),
"body": exc.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)},
)