Sprint 5 : integration et production -- securite, performance, API publique, documentation
Backend: rate limiter, security headers, blockchain cache service avec RPC, public API (7 endpoints read-only), WebSocket auth + heartbeat, DB connection pooling, structured logging, health check DB. Frontend: API retry/timeout, WebSocket auth + heartbeat + typed events, notifications toast, mobile hamburger + drawer, error boundary, offline banner, loading skeletons, dashboard enrichi. Documentation: guides utilisateur complets (demarrage, vote, sanctuaire, FAQ 30+), guide deploiement, politique securite. 123 tests, 155 fichiers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,93 @@
|
||||
import logging
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
from app.database import init_db
|
||||
from app.database import get_db, init_db
|
||||
from app.middleware.rate_limiter import RateLimiterMiddleware
|
||||
from app.middleware.security_headers import SecurityHeadersMiddleware
|
||||
from app.routers import auth, documents, decisions, votes, mandates, protocols, sanctuary, websocket
|
||||
from app.routers import public
|
||||
|
||||
|
||||
# ── Structured logging setup ───────────────────────────────────────────────
|
||||
|
||||
|
||||
def _setup_logging() -> None:
|
||||
"""Configure structured logging based on environment.
|
||||
|
||||
- Production/staging: JSON-formatted log lines for log aggregation.
|
||||
- Development: human-readable format with colors.
|
||||
"""
|
||||
log_level = getattr(logging, settings.LOG_LEVEL.upper(), logging.INFO)
|
||||
|
||||
if settings.ENVIRONMENT in ("production", "staging"):
|
||||
# JSON formatter for structured logging
|
||||
formatter = logging.Formatter(
|
||||
'{"timestamp":"%(asctime)s","level":"%(levelname)s",'
|
||||
'"logger":"%(name)s","message":"%(message)s"}',
|
||||
datefmt="%Y-%m-%dT%H:%M:%S",
|
||||
)
|
||||
else:
|
||||
# Human-readable format for development
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
|
||||
datefmt="%H:%M:%S",
|
||||
)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
handler.setFormatter(formatter)
|
||||
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(log_level)
|
||||
root_logger.handlers.clear()
|
||||
root_logger.addHandler(handler)
|
||||
|
||||
# Reduce noise from third-party libraries
|
||||
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
||||
logging.getLogger("sqlalchemy.engine").setLevel(
|
||||
logging.INFO if settings.ENVIRONMENT == "development" else logging.WARNING
|
||||
)
|
||||
|
||||
|
||||
_setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ── Application lifespan ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
logger.info(
|
||||
"Demarrage %s (env=%s, log_level=%s)",
|
||||
settings.APP_NAME, settings.ENVIRONMENT, settings.LOG_LEVEL,
|
||||
)
|
||||
await init_db()
|
||||
yield
|
||||
logger.info("Arret %s", settings.APP_NAME)
|
||||
|
||||
|
||||
# ── FastAPI application ───────────────────────────────────────────────────
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.APP_NAME,
|
||||
description="Plateforme de decisions collectives pour la communaute Duniter/G1",
|
||||
version="0.1.0",
|
||||
version="0.5.0",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
||||
# ── Middleware stack ──────────────────────────────────────────────────────
|
||||
# Middleware is applied in reverse order: last added = first executed.
|
||||
# Order: SecurityHeaders -> RateLimiter -> CORS -> Application
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.CORS_ORIGINS,
|
||||
@@ -29,6 +96,18 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
RateLimiterMiddleware,
|
||||
rate_limit_default=settings.RATE_LIMIT_DEFAULT,
|
||||
rate_limit_auth=settings.RATE_LIMIT_AUTH,
|
||||
rate_limit_vote=settings.RATE_LIMIT_VOTE,
|
||||
)
|
||||
|
||||
app.add_middleware(SecurityHeadersMiddleware)
|
||||
|
||||
|
||||
# ── Routers ──────────────────────────────────────────────────────────────
|
||||
|
||||
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
|
||||
app.include_router(documents.router, prefix="/api/v1/documents", tags=["documents"])
|
||||
app.include_router(decisions.router, prefix="/api/v1/decisions", tags=["decisions"])
|
||||
@@ -37,8 +116,32 @@ app.include_router(mandates.router, prefix="/api/v1/mandates", tags=["mandates"]
|
||||
app.include_router(protocols.router, prefix="/api/v1/protocols", tags=["protocols"])
|
||||
app.include_router(sanctuary.router, prefix="/api/v1/sanctuary", tags=["sanctuary"])
|
||||
app.include_router(websocket.router, prefix="/api/v1/ws", tags=["websocket"])
|
||||
app.include_router(public.router, prefix="/api/v1/public", tags=["public"])
|
||||
|
||||
|
||||
# ── Health check ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
async def health(db: AsyncSession = Depends(get_db)):
|
||||
"""Health check endpoint that verifies database connectivity.
|
||||
|
||||
Returns status "ok" with database connection info if healthy,
|
||||
or status "degraded" if the database is unreachable.
|
||||
"""
|
||||
try:
|
||||
result = await db.execute(text("SELECT 1"))
|
||||
result.scalar()
|
||||
db_status = "connected"
|
||||
except Exception as exc:
|
||||
logger.warning("Health check: base de donnees inaccessible - %s", exc)
|
||||
db_status = "disconnected"
|
||||
|
||||
overall_status = "ok" if db_status == "connected" else "degraded"
|
||||
|
||||
return {
|
||||
"status": overall_status,
|
||||
"environment": settings.ENVIRONMENT,
|
||||
"database": db_status,
|
||||
"version": "0.5.0",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user