Files
LoreMind/brain/app/main.py
IETM_FIXE\ietm6 4d049274f9
Some checks failed
E2E Tests / e2e (push) Waiting to run
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 26s
Build & Push Images / tests (push) Failing after 13s
Build & Push Images / build (brain) (push) Has been skipped
Build & Push Images / build (core) (push) Has been skipped
Build & Push Images / build (web) (push) Has been skipped
Build & Push Images / build-switcher (push) Has been skipped
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 1m12s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Failing after 1m28s
Refacto du code coté Python, java et coté angular afin de mieux séparer les responsabilité et d'avoir moins de répétitivité dans le code.
Mise en place de tests unitaires coté Python et Angular
Mise en place de la couverture de test directement dans le workflow : le programme ne build pas si jamais un test échoue
Passage en v0.16.2 en conséquence
2026-06-18 15:59:10 +02:00

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