Files
PecHub/backend/app/main.py
T
2026-03-19 11:41:10 +01:00

134 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 auth, mailboxes, messages, notifications, permissions, send, tenants, users, virtual_boxes, ws
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(
"🚀 PecFlow 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("🛑 PecFlow Backend fermato")
# ─── Applicazione FastAPI ─────────────────────────────────────────────────────
limiter = Limiter(key_func=get_remote_address, default_limits=["200/minute"])
app = FastAPI(
title="PecFlow 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)
# ─── 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"},
)