Some checks failed
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 20s
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 29s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Successful in 2m17s
Build & Push Images / build (brain) (push) Successful in 1m13s
E2E Tests / e2e (push) Failing after 5m22s
Build & Push Images / build (core) (push) Successful in 3m25s
Build & Push Images / build-switcher (push) Successful in 51s
Build & Push Images / build (web) (push) Successful in 1m57s
92 lines
3.3 KiB
Python
92 lines
3.3 KiB
Python
"""Point d'entrée FastAPI du Brain LoreMind : assemblage de l'application.
|
|
|
|
Responsabilité UNIQUE : créer l'app, brancher le middleware d'auth inter-service,
|
|
le hook de démarrage et les routers (un par responsabilité, voir `app.api.routers`).
|
|
Toute la logique HTTP vit dans les routers ; la logique métier dans `app.application`.
|
|
"""
|
|
import asyncio
|
|
import hmac
|
|
import logging
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.api.routers import (
|
|
chat,
|
|
generation,
|
|
imports,
|
|
models,
|
|
notebooks,
|
|
settings as settings_router,
|
|
tables,
|
|
)
|
|
from app.core.config import get_settings
|
|
from app.infrastructure.ollama_model_installer import ensure_ollama_embedding_model
|
|
|
|
app = FastAPI(
|
|
title="LoreMind Brain",
|
|
description="Backend IA pour la génération de contenu narratif.",
|
|
version="0.17.0",
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Chemins exemptes d'auth inter-service : healthcheck docker + introspection
|
|
# FastAPI (docs uniquement utiles en dev ; en prod docker-compose, le Brain
|
|
# n'est pas expose en dehors du reseau interne donc pas un risque).
|
|
_PUBLIC_PATHS = frozenset({"/health", "/docs", "/redoc", "/openapi.json"})
|
|
|
|
|
|
@app.middleware("http")
|
|
async def require_internal_secret(request: Request, call_next):
|
|
"""Refuse toute requete qui ne presente pas le secret partage core<->brain.
|
|
|
|
Fail-closed : si `INTERNAL_SHARED_SECRET` n'est pas configure cote Brain,
|
|
TOUTES les requetes non-publiques sont rejetees. Force la configuration
|
|
explicite en prod et empeche un deploiement par defaut non-authentifie.
|
|
|
|
Comparaison en temps-constant via `hmac.compare_digest` pour eviter les
|
|
attaques par timing side-channel sur la validation du secret.
|
|
"""
|
|
if request.url.path in _PUBLIC_PATHS:
|
|
return await call_next(request)
|
|
|
|
expected = get_settings().internal_shared_secret
|
|
provided = request.headers.get("x-internal-secret", "")
|
|
if not expected or not hmac.compare_digest(expected, provided):
|
|
return JSONResponse(
|
|
{"detail": "Unauthorized: invalid or missing X-Internal-Secret"},
|
|
status_code=401,
|
|
)
|
|
return await call_next(request)
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> dict[str, str]:
|
|
"""Sonde de santé — permet au Core Java de vérifier que le Brain répond."""
|
|
return {"status": "ok", "service": "brain"}
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def _auto_install_embedding_model() -> None:
|
|
"""Au démarrage : si le provider d'embeddings est Ollama et que le modèle n'est
|
|
pas installé, on le télécharge EN ARRIÈRE-PLAN → le RAG marche d'emblée pour un
|
|
nouvel utilisateur, sans bloquer le démarrage du Brain. Best-effort (Ollama peut
|
|
être absent / la connexion limitée) ; désactivable via `auto_pull_embedding_model`.
|
|
"""
|
|
settings = get_settings()
|
|
if not settings.auto_pull_embedding_model or settings.embedding_provider != "ollama":
|
|
return
|
|
asyncio.create_task(ensure_ollama_embedding_model(
|
|
settings.ollama_base_url, settings.ollama_embedding_model))
|
|
|
|
|
|
# Un router par responsabilité (SRP) — chemins identiques à l'ancien monolithe.
|
|
app.include_router(generation.router)
|
|
app.include_router(chat.router)
|
|
app.include_router(tables.router)
|
|
app.include_router(imports.router)
|
|
app.include_router(notebooks.router)
|
|
app.include_router(settings_router.router)
|
|
app.include_router(models.router)
|