diff --git a/docs/API_GUIDE.md b/docs/API_GUIDE.md index 694fd2b..cf0ab11 100644 --- a/docs/API_GUIDE.md +++ b/docs/API_GUIDE.md @@ -1,245 +1,200 @@ # Guide d'utilisation de l'API Imago -Bienvenue dans la documentation officielle de l'API REST d'Imago. -**Note :** Tous les terminaux (endpoints) principaux présentés ici sont désormais servis sous le préfixe `/api/v1/` dans l'application locale. +Bienvenue dans la documentation officielle de l'API REST d'Imago v2.0.0. +**Note :** Tous les endpoints sont servis sous le préfixe `/api/v1/`. -## 📍 Sommaire -- [🔐 Authentification](#-Authentification) -- [🛡️ Scopes (Permissions)](#-scopes-permissions) -- [📊 Plans et Rate Limiting](#-plans-et-rate-limiting) -- [👥 Gestion des Clients (Admin uniquement)](#-gestion-des-clients-admin-uniquement) -- [📁 Multi-tenancy et Isolation](#-multi-tenancy-et-isolation) -- [📖 Référence des Endpoints](#-référence-des-endpoints) - - [📸 Gestion des Images](#-gestion-des-images) - - [🤖 Intelligence Artificielle](#-intelligence-artificielle) - - [🔑 Administration (Admin uniquement)](#-administration-admin-uniquement) - - [🏥 Santé et Status](#-santé-et-status) -- [🚀 Exemples Rapides](#-exemples-rapides) +## Sommaire +- [Authentification](#-authentification) +- [Scopes (Permissions)](#-scopes-permissions) +- [Plans et Rate Limiting](#-plans-et-rate-limiting) +- [Multi-tenancy et Isolation](#-multi-tenancy-et-isolation) +- [Gestion des Clients (Admin)](#-gestion-des-clients-admin) +- [Référence des Endpoints](#-reference-des-endpoints) + - [Gestion des Images](#-gestion-des-images) + - [Intelligence Artificielle](#-intelligence-artificielle) + - [Administration](#-administration-admin) + - [Dead Letter Queue](#-dead-letter-queue-admin) + - [Santé et Status](#-sante-et-status) + - [WebSocket (Temps Réel)](#-websocket-temps-reel) +- [Exemples Rapides](#-exemples-rapides) --- -## 🔐 Authentification +## Authentification -Tous les endpoints (sauf `/health` et `/`) nécessitent une authentification via une **Clé API**. +Tous les endpoints (sauf `/health`, `/`, `/metrics`) nécessitent une clé API. -### Header Authorization - -Vous devez inclure votre clé dans le header `Authorization` de chaque requête : - -```http -Authorization: Bearer VOTRE_CLE_API_SECRET +``` +Authorization: Bearer VOTRE_CLE_API ``` -> [!WARNING] -> Traitez votre clé API comme un mot de passe. Ne la partagez jamais et ne l'incluez pas dans du code client (frontend) accessible publiquement. +Ou via le header alternatif : `X-API-Key: VOTRE_CLE_API` + +> La clé API est hashée en SHA-256 + pepper (SECRET_KEY) avant stockage. Les comparaisons sont timing-safe. + +### Clé Master Admin +Définie par `ADMIN_API_KEY` dans `.env`. Cette clé spéciale a tous les droits sans être en base de données. --- -## 🛡️ Scopes (Permissions) +## Scopes (Permissions) -L'accès aux fonctionnalités est contrôlé par des **scopes**. Chaque clé API est limitée à un ensemble de permissions : +| Scope | Accès | +|---|---| +| `images:read` | Lister, voir détails, EXIF, OCR, AI, tags | +| `images:write` | Uploader, relancer le pipeline | +| `images:delete` | Supprimer des images | +| `ai:use` | Résumé d'URL, rédaction de tâches | +| `admin` | Gérer clients, voir stats, DLQ, docs | -| Scope | Description | Fonctions incluses | -|-------|-------------|-------------------| -| `images:read` | Lecture seule | Lister les images, voir les détails, EXIF, OCR, AI. | -| `images:write` | Écriture | Uploader des images, relancer le pipeline de traitement. | -| `images:delete`| Suppression | Supprimer définitivement des images. | -| `ai:use` | Utilisation IA | Résumé d'URL, rédaction de tâches. | -| `admin` | Administration | Gérer les clients API, voir les clés, modifier les plans. | +Les scopes sont validés à la création du client — les valeurs invalides sont rejetées. --- -## 📊 Plans et Rate Limiting +## Plans et Rate Limiting -Le système applique des limites de requêtes (Rate Limits) basées sur le **Plan** de votre client. Les compteurs sont réinitialisés toutes les heures. +Les limites sont dynamiques par plan et isolées par client. Support Redis pour la persistence. -| Plan | Uploads / heure | Requêtes AI / heure | -|------|-----------------|---------------------| +| Plan | Uploads/h | AI req/h | +|---|---|---| | `free` | 20 | 50 | | `standard` | 100 | 200 | | `premium` | 500 | 1000 | -*Les requêtes de lecture (`GET`) ne sont pas limitées par défaut.* - ---- - -## 👥 Gestion des Clients (Admin uniquement) - -Si vous avez le scope `admin`, vous pouvez gérer les accès. - -### Créer un nouveau client +Config : `RATE_LIMIT_STORAGE_URL=redis://redis:***@## Gestion des Clients (Admin) ```bash -curl -X POST http://localhost:8000/auth/clients \ - -H "Authorization: Bearer CLE_ADMIN" \ +# Créer un client +curl -X POST http://localhost:8000/api/v1/auth/clients \ + -H "Authorization: Bearer *** \ -H "Content-Type: application/json" \ - -d '{ - "name": "Application Mobile", - "scopes": ["images:read", "images:write", "ai:use"], - "plan": "standard" - }' + -d '{"name":"App Mobile","scopes":["images:read","images:write","ai:use"],"plan":"standard"}' + +# Rotation de clé +curl -X POST http://localhost:8000/api/v1/auth/clients/{id}/rotate-key \ + -H "Authorization: Bearer *** ``` -**Réponse (Importante) :** -```json -{ - "id": "uuid-...", - "api_key": "cle_generee_en_clair_une_seule_fois", - "name": "Application Mobile", - ... -} -``` -> [!CAUTION] -> La clé API n'est affichée **qu'une seule fois** à la création. Stockez-la immédiatement de manière sécurisée. - -### Régénérer une clé (Rotation) - -En cas de compromission, invalidez l'ancienne clé et générez-en une nouvelle : - -```bash -curl -X POST http://localhost:8000/auth/clients/{id}/rotate-key \ - -H "Authorization: Bearer CLE_ADMIN" -``` +> La clé API n'est affichée qu'une seule fois à la création. Stockez-la immédiatement. --- -## 📁 Multi-tenancy et Isolation +## Multi-tenancy et Isolation -L'API est **multi-tenant**. Cela signifie que : -- Vous ne voyez **que** les images uploadées avec votre clé. -- Les IDs d'images sont globaux, mais si vous tentez d'accéder à l'ID d'un autre client, vous recevrez une erreur `404 Not Found`. -- Vos fichiers physiques sont stockés dans un sous-répertoire dédié sur le serveur (`/data/uploads/{votre_client_id}/`). +- Chaque client voit uniquement ses propres images (filtrage `WHERE client_id = X`) +- Les IDs sont globaux mais l'accès est vérifié → un client B qui tente l'ID du client A reçoit `404` +- Fichiers stockés dans `uploads/{client_id}/` et `thumbnails/{client_id}/` --- -## 📖 Référence des Endpoints +## Reference des Endpoints -### 📸 Gestion des Images +### Gestion des Images -#### Lister les images -`GET /api/v1/images` -- **Scope required** : `images:read` -- **Query Params** : - - `page` : Numéro de page (défaut: 1) - - `page_size` : Taille de page (défaut: 20, max: 100) - - `tag` : Filtrer par tag AI - - `status` : Filtrer par statut (`pending`, `processing`, `done`, `error`) - - `search` : Recherche textuelle dans le nom, la description AI ou l'OCR. +| Méthode | Endpoint | Scope | Description | +|---|---|---|---| +| `POST` | `/api/v1/images/upload` | `images:write` | Upload (multipart) + lancement pipeline AI | +| `GET` | `/api/v1/images` | `images:read` | Lister (pagination, filtres tag/status/search) | +| `GET` | `/api/v1/images/{id}` | `images:read` | Détail complet (EXIF + OCR + AI) | +| `GET` | `/api/v1/images/{id}/status` | `images:read` | Statut du pipeline (pending→done/error) | +| `GET` | `/api/v1/images/{id}/exif` | `images:read` | Métadonnées EXIF + GPS | +| `GET` | `/api/v1/images/{id}/ocr` | `images:read` | Texte extrait par OCR | +| `GET` | `/api/v1/images/{id}/ai` | `images:read` | Description AI + tags + tokens consommés | +| `POST` | `/api/v1/images/{id}/reprocess` | `images:write` | Relancer le pipeline AI | +| `DELETE` | `/api/v1/images/{id}` | `images:delete` | Supprimer image + fichiers | +| `GET` | `/api/v1/images/{id}/download-url` | `images:read` | URL signée temporaire (fichier original) | +| `GET` | `/api/v1/images/{id}/thumbnail-url` | `images:read` | URL signée temporaire (thumbnail) | +| `GET` | `/api/v1/images/tags/all` | `images:read` | Tous les tags uniques du client | -#### Uploader une image -`POST /api/v1/images/upload` -- **Scope required** : `images:write` -- **Body** : `multipart/form-data` - - `file` : Le fichier image (JPEG, PNG, WebP, etc.) -- **Note** : Lance automatiquement le pipeline AI en arrière-plan. +**Recherche :** `GET /api/v1/images?search=motcle&tag=nature&status=done&page=1&page_size=20` -#### Détail complet d'une image -`GET /api/v1/images/{id}` -- **Scope required** : `images:read` -- **Description** : Retourne toutes les données (Source, EXIF, OCR, AI). +### Intelligence Artificielle -#### Statut du traitement -`GET /api/v1/images/{id}/status` -- **Scope required** : `images:read` -- **Description** : Pour savoir si l'analyse par l'IA est terminée. +| Méthode | Endpoint | Scope | Description | +|---|---|---|---| +| `POST` | `/api/v1/ai/summarize` | `ai:use` | Résumé AI d'une URL (scraping + résumé + tags) | +| `POST` | `/api/v1/ai/draft-task` | `ai:use` | Génération de tâche structurée | -#### Métadonnées spécifiques -- `GET /api/v1/images/{id}/exif` : Données techniques de l'appareil et GPS. -- `GET /api/v1/images/{id}/ocr` : Texte extrait de l'image. -- `GET /api/v1/images/{id}/ai` : Description textuelle et tags générés. +L'AI utilise le provider configuré (Gemini ou OpenRouter) avec circuit breaker : timeout configurable (`AI_REQUEST_TIMEOUT`) et retry exponentiel (`AI_MAX_RETRIES`). -#### Retraitement -`POST /api/v1/images/{id}/reprocess` -- **Scope required** : `images:write` -- **Description** : Réinitialise et relance le pipeline d'analyse AI. +### Administration (Admin) -#### Suppression -`DELETE /api/v1/images/{id}` -- **Scope required** : `images:delete` -- **Description** : Supprime l'entrée en base et les fichiers sur le disque. +| Méthode | Endpoint | Description | +|---|---|---| +| `GET` | `/admin/api/stats` | Stats globales (images, stockage, tokens, clients) | +| `GET` | `/admin/api/clients` | Liste des clients | +| `POST` | `/admin/api/clients/{id}/toggle` | Activer/désactiver un client | +| `POST` | `/admin/api/clients/{id}/reset-quota` | Remettre le quota à zéro | +| `GET` | `/admin/api/queue/status` | File d'attente ARQ (pending + dead jobs) | +| `GET` | `/admin/api/docs` | Liste des documents disponibles | +| `GET` | `/admin/api/docs/{filename}` | Contenu d'un document | +| `POST` | `/api/v1/auth/clients` | Créer un client (retourne la clé) | +| `GET` | `/api/v1/auth/clients` | Lister tous les clients | +| `PATCH` | `/api/v1/auth/clients/{id}` | Modifier un client | +| `DELETE` | `/api/v1/auth/clients/{id}` | Désactiver un client | -#### Tags globaux -`GET /api/v1/images/tags/all` -- **Scope required** : `images:read` -- **Description** : Liste tous les tags uniques utilisés par le client. +### Dead Letter Queue (Admin) + +Jobs échoués après `PIPELINE_MAX_RETRIES` tentatives. + +| Méthode | Endpoint | Description | +|---|---|---| +| `GET` | `/admin/api/queue/dead?limit=50` | Lister les jobs morts | +| `POST` | `/admin/api/queue/dead/{index}/retry` | Relancer un job mort | +| `DELETE` | `/admin/api/queue/dead` | Vider la DLQ | + +### Santé et Status + +Endpoints publics (pas d'authentification). + +| Méthode | Endpoint | Description | +|---|---|---| +| `GET` | `/` | Version et statut | +| `GET` | `/health` | Santé (AI, OCR, provider) | +| `GET` | `/health/detailed` | Diagnostic complet (BDD, Redis, ARQ, MinIO, OCR, AI, worker) | +| `GET` | `/metrics` | Métriques Prometheus | + +### WebSocket (Temps Réel) + +| Endpoint | Auth | Description | +|---|---|---| +| `ws://host/ws/pipeline/{image_id}?token=API_KEY` | API Key | Événements live du pipeline + buffer 60s | +| `ws://host/ws/admin/monitor?token=ADMIN_KEY` | Admin | Monitoring global de tous les pipelines | --- -### 🤖 Intelligence Artificielle +## Exemples Rapides -#### Résumé d'URL -`POST /api/v1/ai/summarize` -- **Scope required** : `ai:use` -- **Body** (JSON) : - ```json - { - "url": "https://...", - "language": "français" - } - ``` - -#### Rédaction de tâche -`POST /api/v1/ai/draft-task` -- **Scope required** : `ai:use` -- **Body** (JSON) : - ```json - { - "description": "Texte libre décrivant la tâche", - "context": "Contexte optionnel", - "language": "fr" - } - ``` - ---- - -### 🔑 Administration (Admin uniquement) -*Nécessite le scope `admin`.* - -- `POST /api/v1/auth/clients` : Créer un client (retourne la clé). -- `GET /api/v1/auth/clients` : Lister tous les clients. -- `GET /api/v1/auth/clients/{id}` : Voir les détails d'un client. -- `PATCH /api/v1/auth/clients/{id}` : Modifier un client (nom, scopes, plan). -- `POST /api/v1/auth/clients/{id}/rotate-key` : Changer la clé API. -- `DELETE /api/v1/auth/clients/{id}` : Désactiver un client (suspension d'accès). - ---- - -### 🏥 Santé et Status -Ces endpoints sont **publics** et ne nécessitent aucune clé API. - -- `GET /` : Informations de base sur l'application (Version, Status). -- `GET /health` : Vérification complète de l'état (AI configurée, OCR actif, Modèle utilisé). - ---- - -## 🚀 Exemples Rapides - -### Lister mes images (Python / httpx) +### Upload + suivi pipeline (Python) ```python -import httpx +import httpx, asyncio, websockets, json -headers = {"Authorization": "Bearer ma_super_cle"} -r = httpx.get("http://localhost:8000/images", headers=headers) +API = "http://localhost:8000" +KEY = "ma_cle_api" +headers = {"Authorization": f"Bearer {KEY}"} -for img in r.json()["items"]: - print(f"ID: {img['id']} | Name: {img['original_name']}") -``` +async def main(): + # Upload + files = {"file": open("photo.jpg", "rb")} + r = httpx.post(f"{API}/api/v1/images/upload", files=files, headers=headers) + img = r.json() + print(f"Uploaded: {img['id']}") -### Uploader une image (Node.js / Axios) + # Suivi WebSocket du pipeline + async with websockets.connect( + f"ws://localhost:8000/ws/pipeline/{img['id']}?token={KEY}" + ) as ws: + async for msg in ws: + event = json.loads(msg) + print(f"Pipeline: {event['event']}") + if event["event"] in ("pipeline.done", "pipeline.error"): + break -```javascript -const axios = require('axios'); -const fs = require('fs'); -const FormData = require('form-data'); + # Résultat final + detail = httpx.get(f"{API}/api/v1/images/{img['id']}/ai", headers=headers).json() + print(f"Description: {detail['description']}") + print(f"Tags: {detail['tags']}") -const form = new FormData(); -form.append('file', fs.createReadStream('vacances.jpg')); - -axios.post('http://localhost:8000/images/upload', form, { - headers: { - ...form.getHeaders(), - 'Authorization': 'Bearer ma_super_cle' - } -}).then(console.log); +asyncio.run(main()) ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 56f891c..ddb60a4 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,60 +1,118 @@ -# Architecture Technique - Imago Hub +# Architecture Technique — Imago Hub v2.0.0 -Ce document détaille l'architecture logicielle, les flux de données et les choix technologiques d'Imago Hub. Il s'adresse aux développeurs, architectes et contributeurs du projet. +Ce document détaille l'architecture logicielle, les flux de données et les choix technologiques. --- -## 🏗️ 1. Vue d'Ensemble des Composants +## 1. Vue d'Ensemble -Imago s'articule autour de plusieurs services distincts orchestrés via Docker, communiquant par des interfaces asynchrones et résilientes. - -### La Stack Technique -- **Backend API Layer :** `FastAPI` (Python 3.10+) pour des performances maximales, des endpoints asynchrones (`async/await`) et une validation native (Pydantic). L'API répond vite, elle ne bloque jamais sur l'IO réseau. -- **Frontend Admin :** `React` (JavaScript/TypeScript) propulsé par Vite et servi via Nginx. Interface Single-Page minimaliste, rapide. -- **Worker Queue :** `ARQ` (Async Redis Queue). Une alternative moderne s'appuyant sur l'Event Loop natif d'asyncio Python au lieu de frameworks lourds. -- **Broker & Cache :** `Redis` stocke les jobs temporaires, limite les taux (Rate Limiting) et gère le pub/sub des requêtes websocket. -- **Base de données :** `PostgreSQL` interrogé via `SQLAlchemy` (orm asynchrone : `asyncpg`). -- **Objets & Médias :** `MinIO` implémentant le standard S3 (ou un volume filesystem classique) pour isoler les données binaires massives. +| Composant | Technologie | Rôle | +|---|---|---| +| API Backend | FastAPI (Python 3.12+) + Uvicorn | Endpoints REST async, validation Pydantic | +| Frontend Admin | React + TypeScript + Vite + Nginx | Interface d'administration | +| Worker Queue | ARQ (Async Redis Queue) | Traitement asynchrone du pipeline AI | +| Broker/Cache | Redis 7 | Jobs ARQ, Pub/Sub WebSocket, Rate Limiting, DLQ | +| Base de données | PostgreSQL 16 + SQLAlchemy async | Données relationnelles, multi-tenant | +| Stockage objets | MinIO (S3-compatible) ou stockage local | Fichiers binaires (images, thumbnails) | +| Observabilité | structlog + Prometheus + Grafana | Logs structurés, métriques, dashboards | --- -## 🔄 2. Flux de Traitement Asynchrone (Le Pipeline) +## 2. Flux de Traitement (Pipeline AI) -Toute la valeur d'Imago réside dans sa capacité à dépiler un flux lourd d'images de façon déconnectée des appels HTTP des utilisateurs finaux. +``` +Client → POST /api/v1/images/upload + → Validation MIME + quota + → Sauvegarde fichier (S3 ou local) + thumbnail + → Insert BDD (status: PENDING) + → Enqueue job ARQ: process_image_task(image_id, client_id) + → Réponse 201 au client -### Chronologie d'un événement `Upload` -1. Le client effectue un `POST /upload`. L'API FastAPI, en streaming mémoire, pousse l'image vers le backend de stockage (S3). -2. L'API enregistre dans PostgreSQL l'image avec un statut `PENDING`. -3. L'API publie un message **enqueue** vers ARQ/Redis : `analyse_image(db_id=124)`. -4. L'API répond immédiatement au client HTTP avec `202 Accepted` et l'UUID généré. +Worker ARQ (processus séparé): + → status: PROCESSING + → Étape 1: EXIF (piexif) → métadonnées appareil, GPS + → Étape 2: OCR (Tesseract) → texte + fallback AI OCR + → Étape 3: Vision AI (Gemini/OpenRouter) → description, tags + → status: DONE (ou ERROR si échec après N tentatives) + → Publication Redis Pub/Sub → WebSocket clients +``` -### Boucle du Worker ARQ (processus séparé) -Une fois le message attrapé par le démon `worker.py` : -1. **Verrouillage** : Le statut passe à `PROCESSING`. -2. **Étape EXIF** : (via `exifread` / modules Python) analyse purement metadata, parse le GPS et écrit dans les tables reliées. -3. **Étape OCR** : Tesseract est encapsulé. Il scanne l'image et met à jour l'entité PostgreSQL. -4. **Étape IA Vision** : Un client HTTP asynchrone (`httpx`) effectue un appel API vers Google (Gemini) ou OpenRouter. Une demande de structuration en prompt pur JSON assure des typages clairs. -5. **Clôture** : Le statut passe à `COMPLETED`. - -*(En cas d'échec sur une étape isolée comme le réseau IA, le worker utilise la retry-policy asynchrone native d'ARQ).* +**Résilience :** +- Chaque étape est indépendante — un échec partiel n'arrête pas le pipeline +- Circuit breaker AI : timeout configurable + retry exponentiel +- Dead Letter Queue : jobs échoués après `PIPELINE_MAX_RETRIES` → Redis DLQ inspectable --- -## 🗄️ 3. Modèle de Données & Multi-tenancy +## 3. Modèle de Données -La BDD relationnelle s'articule pour cloisonner (isolation de locataire) les données de façon logicielle : +### Table `api_clients` +- `id` (UUID), `name`, `api_key_hash` (SHA-256 + pepper) +- `scopes` (JSON), `plan` (free/standard/premium) +- `storage_used_bytes`, `quota_storage_mb`, `quota_images` +- `is_active` (soft delete) -- **Table Clients** : Représente le "Tenant". Un Client possède plusieurs clés API hashées (Bcrypt) et un "plan" définissant ses quotas. -- **Table Image** : Clé principale. Chaque Image possède une `client_id` liant la ressource directement au Tenant. C'est l'essence du filtrage (Aucune requête RDBM vers Images ne démarre sans `WHERE client_id = X`). -- **Tables Satellites (Exif, OCR, AITasks)** : Héritent des dépendances (Cascade Deletes) liant l'information technique à l'uuid Image. - -### Le Rate Limiting Distribué -Le limiter applicatif s'appuie sur `slowapi` et le backend Redis. L'ID du tenant (Client Object) sert de clé dans Redis pour évaluer le bucket algorithmique : -- Permet de bloquer avant l'appel FastAPI les clients abusifs en respectant strictement l'architecture distribuée. +### Table `images` +- `id`, `uuid`, `client_id` → isolation multi-tenant +- `original_name`, `file_path`, `thumbnail_path`, `mime_type`, `file_size` +- `processing_status` (pending/processing/done/error) +- **EXIF** : `exif_make`, `exif_model`, `exif_gps_lat/lon`, `exif_taken_at`, etc. +- **OCR** : `ocr_text`, `ocr_language`, `ocr_confidence` +- **AI** : `ai_description`, `ai_tags` (JSON), `ai_model_used`, `ai_prompt_tokens`, `ai_output_tokens` +- Indexes composites : `(client_id, uploaded_at)`, `(client_id, processing_status)` --- -## 🔌 4. Observabilité et Temps Réel +## 4. Sécurité -- **FastAPI Websockets Pub/Sub :** L'API monte des sessions WebSockets bidirectionnelles. Lors d'un changement dans PostgreSQL (Via un event déclenché par ARQ en fin de boucle), un hook publie un patch JSON aux websockets écoutants. L'UI (Shaarli / Admin) est ré-invalider sans actualisation HTTP. -- **Prometheus Metrics :** Le endpoint `/metrics` expose via un middleware des données quantitatives brutes de latence et volumétries. Facilement gratté (scraped) par des tableaux Grafana ou Datadog. +| Couche | Mécanisme | +|---|---| +| Authentification | API Key via `Authorization: Bearer ` ou `X-API-Key` | +| Hashage | SHA-256 + pepper (SECRET_KEY) — comparaison timing-safe | +| Master Key | `ADMIN_API_KEY` configurable, bypass BDD avec tous les droits | +| Autorisation | Scopes obligatoires par endpoint (`require_scope("images:write")`) | +| Validation | Scopes rejetés si invalides (`VALID_SCOPES`) | +| Rate Limiting | slowapi par client_id + plan, stockage Redis optionnel | +| CORS | Configurable, rejette `["*"]` si `allow_credentials=True` | +| URLs signées | HMAC via itsdangerous (local) ou S3 presigned URLs | + +--- + +## 5. Observabilité + +- **structlog** : JSON en production, console colorée en dev. Trace ID (`X-Request-ID`) injecté. +- **Prometheus** : `/metrics` — counters (uploads, tokens, erreurs), histograms (durée pipeline), gauges (stockage, WebSocket) +- **Health checks** : `/health` (simple), `/health/detailed` (BDD, Redis, ARQ, MinIO, OCR, AI, worker) +- **Grafana** : dashboard 10 panneaux fourni (`docs/grafana-dashboard.json`) + +--- + +## 6. WebSocket — Temps Réel + +- `/ws/pipeline/{image_id}?token=API_KEY` — événements live du pipeline +- `/ws/admin/monitor?token=ADMIN_KEY` — monitoring global admin +- Buffer Redis 60s (10 derniers événements) pour reconnexion +- Pub/Sub Redis : `pipeline:{image_id}` et `pipeline:admin` + +--- + +## 7. Infrastructure & Déploiement + +### Développement local +```bash +docker compose up -d # PostgreSQL + Redis + MinIO + API + Worker +``` + +### Production (serveur lab) +L'image est poussée vers le registry local : +``` +docker-registry.dev.home:5000/imago-backend:latest +``` +Déploiement : `docker-compose.production.yml` + `.env.production` + +### Build & Push +```powershell +cd docker +.\build-img.ps1 # Build via WSL Debian +.\deploy-img.ps1 # Push vers registry (semver auto) +```