mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 20:55:41 +02:00
139 lines
5.3 KiB
Python
139 lines
5.3 KiB
Python
"""
|
||
Entrypoint FastAPI – registra router, middleware, startup/shutdown.
|
||
"""
|
||
|
||
from contextlib import asynccontextmanager
|
||
from typing import AsyncGenerator
|
||
|
||
from fastapi import FastAPI, Request, status
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.responses import JSONResponse
|
||
from slowapi import Limiter, _rate_limit_exceeded_handler
|
||
from slowapi.errors import RateLimitExceeded
|
||
from slowapi.middleware import SlowAPIMiddleware
|
||
from slowapi.util import get_remote_address
|
||
|
||
from app.api.v1 import audit_log, auth, labels, mailboxes, messages, notifications, permissions, reports, send, tenants, users, virtual_boxes, ws
|
||
from app.api.v1 import settings as settings_router
|
||
from app.config import get_settings
|
||
from app.core.logging import get_logger, setup_logging
|
||
from app.database import engine
|
||
from app.websocket.manager import redis_subscriber_loop
|
||
|
||
settings = get_settings()
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||
"""Gestione ciclo di vita dell'applicazione."""
|
||
import asyncio
|
||
|
||
setup_logging()
|
||
logger.info(
|
||
"🚀 PEChub Backend avviato",
|
||
extra={"env": settings.app_env, "debug": settings.app_debug},
|
||
)
|
||
|
||
# Avvia il subscriber Redis per il forward degli eventi WebSocket
|
||
redis_task = asyncio.create_task(
|
||
redis_subscriber_loop(settings.redis_url),
|
||
name="redis-ws-subscriber",
|
||
)
|
||
|
||
yield
|
||
|
||
# Cleanup
|
||
redis_task.cancel()
|
||
try:
|
||
await redis_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
# Chiudi pool arq se aperto
|
||
from app.services.send_service import close_arq_pool
|
||
await close_arq_pool()
|
||
await engine.dispose()
|
||
logger.info("🛑 PEChub Backend fermato")
|
||
|
||
|
||
# ─── Applicazione FastAPI ─────────────────────────────────────────────────────
|
||
limiter = Limiter(key_func=get_remote_address, default_limits=["200/minute"])
|
||
|
||
app = FastAPI(
|
||
title="PEChub API",
|
||
description="API per la gestione PEC SaaS multi-tenant",
|
||
version="1.0.0",
|
||
docs_url="/docs" if not settings.is_production else None,
|
||
redoc_url="/redoc" if not settings.is_production else None,
|
||
lifespan=lifespan,
|
||
)
|
||
|
||
# ─── Middleware ───────────────────────────────────────────────────────────────
|
||
app.state.limiter = limiter
|
||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||
app.add_middleware(SlowAPIMiddleware)
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=settings.cors_origins_list,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# ─── Router ───────────────────────────────────────────────────────────────────
|
||
API_PREFIX = "/api/v1"
|
||
|
||
app.include_router(auth.router, prefix=API_PREFIX)
|
||
app.include_router(users.router, prefix=API_PREFIX)
|
||
app.include_router(tenants.router, prefix=API_PREFIX)
|
||
app.include_router(permissions.router, prefix=API_PREFIX)
|
||
app.include_router(mailboxes.router, prefix=API_PREFIX)
|
||
app.include_router(messages.router, prefix=API_PREFIX)
|
||
app.include_router(send.router, prefix=API_PREFIX)
|
||
app.include_router(ws.router, prefix=API_PREFIX)
|
||
app.include_router(virtual_boxes.router, prefix=API_PREFIX)
|
||
app.include_router(notifications.router, prefix=API_PREFIX)
|
||
app.include_router(labels.router, prefix=API_PREFIX)
|
||
app.include_router(settings_router.router, prefix=API_PREFIX)
|
||
app.include_router(reports.router, prefix=API_PREFIX)
|
||
app.include_router(audit_log.router, prefix=API_PREFIX)
|
||
|
||
|
||
# ─── Health check ─────────────────────────────────────────────────────────────
|
||
@app.get("/health", tags=["Health"], include_in_schema=False)
|
||
async def health_check() -> dict:
|
||
"""Endpoint di health check per Docker/Kubernetes."""
|
||
return {
|
||
"status": "ok",
|
||
"version": "1.0.0",
|
||
"env": settings.app_env,
|
||
}
|
||
|
||
|
||
@app.get("/health/db", tags=["Health"], include_in_schema=False)
|
||
async def health_db() -> dict:
|
||
"""Verifica connessione al database."""
|
||
from sqlalchemy import text
|
||
from app.database import AsyncSessionLocal
|
||
|
||
try:
|
||
async with AsyncSessionLocal() as session:
|
||
await session.execute(text("SELECT 1"))
|
||
return {"status": "ok", "database": "connected"}
|
||
except Exception as e:
|
||
return JSONResponse(
|
||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||
content={"status": "error", "database": str(e)},
|
||
)
|
||
|
||
|
||
# ─── Error handler globale ────────────────────────────────────────────────────
|
||
@app.exception_handler(Exception)
|
||
async def global_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
||
logger.exception(f"Errore non gestito: {exc}")
|
||
return JSONResponse(
|
||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||
content={"detail": "Errore interno del server"},
|
||
)
|