mirror of
https://github.com/idrainformatica/PecFlow.git
synced 2026-06-16 12:45:42 +02:00
feat: Fase 1 – Fondamenta complete (backend FastAPI + auth + permessi)
- 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
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
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"},
|
||||
)
|
||||
Reference in New Issue
Block a user