Architecture — Backend
Architecture — Backend
Dernière mise à jour : 2026-05-10
Couches
src/
├── routes/ # Contrats HTTP (Hono), validation Zod, auth
├── handlers/ # Logique métier pure (Phase 4 MVC)
├── services/ # Domaine RAG / Qdrant / TEI / Claude / cards / méta / OCR
├── repositories/ # Data access Drizzle — SEUL endroit qui importe drizzle-orm
├── middleware/ # auth.ts (sessions, roles), security.ts (CORS, headers)
├── lib/ # logger, schemas Zod partagés, utils, with-timeout
├── cron/ # ingest-scheduler, meta-sync, forum-sync
├── config.ts # Validation Zod env vars (boot-time)
├── schema.ts # Tables Drizzle (users, games, questions)
├── db.ts # Connexion SQLite + migrations
├── types.ts # Types globaux (SessionUser, AppEnv)
└── index.ts # App Hono, montage routes, CORS, init
Règles d'archi
routes/ne fait que parser/valider l'entrée (Zod), appeler un handler ou un service, renvoyer la réponse Hono.handlers/ne connaît pas Hono. Reçoit des données validées + dépendances, retourne unResultdiscriminé. Pour les erreurs attendues :type DeleteGameResult = { ok: true } | { ok: false; status: 404; error: string }.services/: logique métier, pas de DB directe — passe par les repos.repositories/: SEUL endroit qui importedb,drizzle-ormou les tables du schéma. Nommer les fonctions par intention métier (getByBggId,setIngestStatus).config.ts: seul endroit qui peut lireprocess.env.X. Tous les autres fichiers fontimport { config }.logger.ts: seul endroit oùconsole.*est autorisé. Convention : préfixer le scope dans le message (logger.info('[meta-sync] ...')).- Aucun fichier > 350 lignes. Si un fichier enfle, le découper :
types.tspour les interfaces, un fichier par responsabilité,index.tscomme barrel. - Imports services : toujours via le barrel (
from '../services/rag/index.js'), jamais un sous-module direct.
Routes (vue d'ensemble)
| Préfixe | Module |
|---|---|
/api/auth/* |
routes/auth.ts — register, login, logout, change password, reset |
/api/games/* |
routes/games.ts — CRUD jeux, PDF, ingest, page-image |
/api/ask/* |
routes/ask.ts — RAG retrieve + stream + feedback |
/api/cards/* |
routes/cards.ts — autocomplete, image proxy |
/api/decks/parse |
routes/decks.ts — import deck FAB |
/api/bgg/* |
routes/bgg.ts — search, hot, expansions |
/api/lorcana-symbols/* |
routes/lorcana-symbols.ts |
/api/admin/* |
routes/admin.ts — health, users, feedback, sync cards |
/api/health |
routes/health.ts — pour healthcheck Docker |
/api/confirm-user/:token |
routes/auth.ts — lien email confirmation |
Tableau exhaustif des 34 endpoints : voir architecture/api-rest.md.
Middleware
auth.ts:requireAuth: 401 si pas de sessionrequireConfirmed: 403 si role='pending'requireAdmin: 403 si role≠'admin'requireCanAddGames: 403 si user.canAddGames=false (admins bypass)
security.ts: CORS whitelist (origines exactes + CIDR IPv4 LAN), headers sécurité de base. La majorité des headers (HSTS, CSP) sont gérés par NPM en amont.
Services principaux
services/
├── qdrant.ts # Client Qdrant (search, upsert, scroll, health)
├── tei.ts # Client TEI bge-m3
├── reranker.ts # Client TEI reranker
├── claude-ssh.ts # SSH Claude Code avec streaming JSON
├── claude-local.ts # Mode dev local (bypass SSH)
├── claude-quota.ts # Détection ClaudeQuotaError + parse resetAt
├── validate-model.ts # Regex stricte modèles Claude (anti-injection shell)
├── bm25.ts # BM25 sparse retrieval (Qdrant natif)
├── email.ts # SMTP (notif admin, password reset)
├── bgg.ts + bgg-forums.ts # API BoardGameGeek + scrape forums Rules
├── hierarchy.ts # LLM hiérarchie chapter/section
├── conflict-detect.ts # Détecte conflits extension/base via similarité + LLM
├── contextual-cache.ts # Cache JSON contextes générés
├── contextual-llm.ts # LLM contextuel par chunk (Contextual Retrieval B)
├── cards-cache.ts # Cache mémoire cartes + recherche par nom
├── cards-sync.ts # Sync collections Qdrant vs sources locales
├── query-expand.ts # Expansion query (HyDE, synonymes)
├── pdf-images.ts # Rendu PDF→PNG via pdftoppm (300 DPI)
│
├── chunking/ # Pipeline chunking (chunker, contextual, pdf-extract, types)
├── ocr/ # Phase 1 : tesseract auto (decideOCR, ocrPages, cache)
├── qdrant/ # Wrappers bas niveau (client, collections, points, payload)
├── ingest/ # Machine à états (queue, coordinator, stages)
├── rag/ # Pipeline RAG (retrieve, answer, classify, decompose, deckbuilding)
├── cards/sources/ # Normalisations par TCG (magic, lorcana, fab, riftbound, tm, ark-nova) + registry
└── meta/ # Méta-game (17lands, mtggoldfish, mtgtop8, mobalytics, fabtcg, riftboundstats, ingest)
Patterns importants
withTimeout(promise, ms, label): wrap toute promesse externe (HyDE, decompose, embeddings) pour ne jamais bloquer un boot ou une réponse RAG.- Pool SSH parallèle (10 workers) : pattern standard pour Contextual Retrieval B et conflict detection. Chaque worker check
quotaErroravant de poll la queue. - EventPusher : signature des stages d'ingestion
(game, ..., push: EventPusher) => Promise<...>. Les stages ne connaissent rien de l'IngestJob. - Heartbeat SSE : tout endpoint streamSSE qui peut rester silencieux >30s ouvre un
setIntervalqui émet{ type: 'heartbeat' }toutes les 8s.
No comments to display
No comments to display