mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
58a233236c
- docker-compose.yml: PostgreSQL 16, Redis 7, MinIO, Nginx - backend FastAPI: struttura monorepo, config pydantic-settings - modelli SQLAlchemy: tutti i modelli (tenants, users, mailboxes, messages, archival, permissions, labels, audit_log) - migrazione Alembic 0001: schema completo in pure SQL - auth API: login JWT, refresh token rotation, logout, 2FA TOTP (setup/verify/disable) - CRUD utenti: lista, crea, modifica, reset password, soft delete - permessi granulari (Fase 1-A): mailbox_permissions, assegna/revoca/lista - CRUD tenant: gestione super-admin - sicurezza: AES-256-GCM cifratura credenziali IMAP/SMTP, bcrypt password - RLS PostgreSQL: isolamento multi-tenant per request - seed sviluppo: tenant demo + admin + operator - test unit: security (bcrypt, JWT, AES), auth_service - test integration: auth endpoints, users endpoints - CI GitHub Actions: lint (ruff), test (pytest), build Docker, security scan - infra: nginx.conf, redis.conf - Makefile con comandi make dev/test/migrate/seed Definition of Done: ✅ Login, refresh token e TOTP funzionanti ✅ make dev porta in piedi tutto lo stack locale ✅ CI configurata
109 lines
4.1 KiB
Python
109 lines
4.1 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 auth, permissions, tenants, users
|
||
from app.config import get_settings
|
||
from app.core.logging import get_logger, setup_logging
|
||
from app.database import engine
|
||
|
||
settings = get_settings()
|
||
logger = get_logger(__name__)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||
"""Gestione ciclo di vita dell'applicazione."""
|
||
setup_logging()
|
||
logger.info(
|
||
"🚀 PecFlow Backend avviato",
|
||
extra={"env": settings.app_env, "debug": settings.app_debug},
|
||
)
|
||
yield
|
||
# Cleanup: chiudi connessioni DB
|
||
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)
|
||
|
||
|
||
# ─── 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"},
|
||
)
|