133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
import asyncio
|
|
import os
|
|
from contextlib import asynccontextmanager
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from slowapi import _rate_limit_exceeded_handler
|
|
from slowapi.errors import RateLimitExceeded
|
|
|
|
from app.api.v1 import api_v1_router
|
|
from app.core.config import get_settings
|
|
from app.core.database import async_session_maker, engine
|
|
from app.core.limiter import limiter
|
|
from app.core.logging_middleware import DebugLoggingMiddleware
|
|
from app.core.middleware import SecurityHeadersMiddleware
|
|
from app.core.seed import seed_database
|
|
from app.services.debug_service import DebugService
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
# Seed database on startup (idempotent — skips if data exists)
|
|
try:
|
|
async with async_session_maker() as db:
|
|
await seed_database(db)
|
|
except Exception:
|
|
pass # DB may not be ready yet, seed will run on next restart
|
|
|
|
# Auto-cleanup logs on startup
|
|
try:
|
|
async with async_session_maker() as db:
|
|
svc = DebugService(db)
|
|
await svc.auto_cleanup_if_needed()
|
|
except Exception:
|
|
pass
|
|
|
|
# Sync bot commands on startup
|
|
try:
|
|
async with async_session_maker() as db:
|
|
from app.integrations.telegram.sync_service import TelegramBotSyncService
|
|
from app.integrations.telegram.client import TelegramBotClient
|
|
bot_client = TelegramBotClient(token=settings.telegram_bot_token)
|
|
svc = TelegramBotSyncService(db, bot_client)
|
|
await svc.full_sync()
|
|
except Exception:
|
|
pass
|
|
|
|
# Auto-run QATesterAgent after fresh deploy (within 5 min)
|
|
async def _auto_qa():
|
|
try:
|
|
deploy_stamp = "/opt/voidea/.last_deploy"
|
|
if not os.path.exists(deploy_stamp):
|
|
return
|
|
with open(deploy_stamp) as f:
|
|
ts = int(f.read().strip())
|
|
if (datetime.now().timestamp() - ts) > 300:
|
|
return # older than 5 min
|
|
|
|
await asyncio.sleep(3)
|
|
async with async_session_maker() as db:
|
|
from app.agents.qa_tester_agent import QATesterAgent
|
|
agent = QATesterAgent(session=db)
|
|
result = await agent.run({"action": "api", "test_count": 2})
|
|
if not result.success:
|
|
logger = __import__("logging").getLogger("voidea.qa_auto")
|
|
logger.warning("Auto-QA after deploy: %s", result.message)
|
|
except Exception:
|
|
pass
|
|
|
|
asyncio.create_task(_auto_qa())
|
|
|
|
yield
|
|
await engine.dispose()
|
|
|
|
|
|
app = FastAPI(
|
|
title=settings.project_name,
|
|
description="VoIdeaAI — идеи рождаются вслух, решения приходят мгновенно!",
|
|
version=settings.project_version,
|
|
docs_url="/docs",
|
|
redoc_url="/redoc",
|
|
openapi_url="/openapi.json",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.state.limiter = limiter
|
|
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
|
|
|
app.include_router(api_v1_router)
|
|
|
|
app.mount("/assets", StaticFiles(directory="webui/dist/assets"), name="assets")
|
|
|
|
app.mount("/icons", StaticFiles(directory="webui/dist/icons"), name="icons")
|
|
|
|
app.mount("/", StaticFiles(directory="webui/dist", html=True), name="static")
|
|
|
|
app.add_middleware(SecurityHeadersMiddleware)
|
|
|
|
app.add_middleware(DebugLoggingMiddleware)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origins_list,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.get("/health")
|
|
@limiter.limit("30/minute")
|
|
async def health_check(request: Request):
|
|
return {
|
|
"status": "healthy",
|
|
"version": settings.project_version,
|
|
"environment": settings.project_env,
|
|
}
|
|
|
|
|
|
@app.get("/api/v1/health")
|
|
@limiter.limit("30/minute")
|
|
async def api_health(request: Request):
|
|
return {
|
|
"status": "healthy",
|
|
"api_version": "v1",
|
|
"database": "connected",
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
}
|