Files
Api-DataSentinel/main.py
T
Antho 934e669774 refactor(api): découpe main.py en routeurs par domaine
main.py comptait 829 lignes et regroupait 9 domaines fonctionnels.
Il ne fait plus que l'assemblage de l'application (configuration,
middlewares, montage des routeurs), soit 115 lignes.

- routers/ : un module par domaine, chacun déclarant sa propre
  dépendance d'authentification
- domain.py : enums UserRole et AuditAction, mapping MONITO_TABLES ;
  les rôles étaient jusqu'ici répétés en dur à deux endroits
- helpers.py : conversion des lignes pyodbc, écriture du journal
- rate_limit.py : limiteur partagé, isolé pour éviter un import
  circulaire entre main.py et le routeur d'authentification

Les codes HTTP littéraux (404, 401, 400, 201) passent aux constantes
fastapi.status, comme le faisait déjà auth.py. Les actions du journal
d'audit passent en paramètre SQL au lieu d'être concaténées.

Aucune route modifiée : la comparaison des specs OpenAPI avant/après
confirme que les 26 URL existantes sont identiques.

conftest patchait main.get_cursor ; chaque routeur important désormais
get_cursor dans son propre espace de noms, la fixture remplace le nom
dans tous les modules concernés.
2026-08-15 14:28:45 +02:00

116 lines
3.6 KiB
Python

# ============================================================
# 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."""
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,
"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)