# ============================================================ # main.py — API FastAPI Data Sentinel # COYAUD Anthony | 2026 # V2 : une table dédiée par monitoring # # Ce module ne contient que l'assemblage de l'application : # configuration, middlewares et montage des routeurs. Chaque # domaine fonctionnel vit dans routers/. # # Lancement : uvicorn main:app --reload --port 8000 # Swagger : http://localhost:8000/docs # ============================================================ import os from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from slowapi import _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded from config import Config, get_cursor from domain import MONITO_TABLES from rate_limit import limiter from routers import ( admin, authentification, dashboard, evolution, historique, monitorings, referentiels, rgpd, ) # Origines autorisées : depuis CORS_ORIGINS (séparées par des virgules) en prod, # localhost par défaut en dev. CORS_ORIGINS = [ o.strip() for o in os.getenv("CORS_ORIGINS", "http://localhost:5173,http://localhost:3000").split(",") if o.strip() ] # ============================================================ # Initialisation # ============================================================ app = FastAPI( title = Config.API_TITLE, version = Config.API_VERSION, description = Config.API_DESCRIPTION, ) # Limiteur de débit (anti brute-force sur /auth/login). app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_middleware( CORSMiddleware, allow_origins = CORS_ORIGINS, allow_methods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers = ["*"], allow_credentials = True, ) # En-têtes de sécurité sur toutes les réponses. @app.middleware("http") async def security_headers(request: Request, call_next): response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" return response # ============================================================ # SANTÉ — route publique # ============================================================ @app.get("/health", tags=["Système"]) def health_check(): """ Ping API + test connexion SQL Server. `build` porte le SHA du commit dont l'image a été construite (injecté par la CI). C'est le seul moyen fiable de vérifier qu'un déploiement a bien pris : comparer ce champ au dernier commit poussé. """ try: with get_cursor() as cursor: cursor.execute("SELECT 1") cursor.fetchone() db_status = "ok" except Exception as e: db_status = f"error: {str(e)}" return { "api" : "ok", "database" : db_status, "version" : Config.API_VERSION, "build" : Config.BUILD, "nb_monitorings" : len(MONITO_TABLES), } # ============================================================ # Montage des routeurs # # Les routeurs de données déclarent eux-mêmes leur dépendance # d'authentification. /health et /auth/login restent publics. # ============================================================ app.include_router(authentification.router) app.include_router(referentiels.router) app.include_router(monitorings.router, prefix="/monitorings") app.include_router(dashboard.router, prefix="/dashboard") app.include_router(historique.router, prefix="/historique") app.include_router(evolution.router, prefix="/evolution") app.include_router(admin.router) app.include_router(rgpd.router)