Add admin panel, WebSocket support, and API versioning
CI / Lint & Format (push) Failing after 28s
CI / Tests (push) Has been skipped
CI / Security Scan (push) Failing after 8s
CI / Docker Build (push) Has been skipped

Introduce an admin portal (React + Nginx), WebSocket routing, and
API versioning middleware with `/api/v1/` prefix deprecation.
Add master API key authentication, new Prometheus metrics for AI
token consumption and active WebSockets, and extend S3 config
with a public endpoint URL. Update test paths and fixtures to
align with the new routing structure.
This commit is contained in:
2026-06-22 11:25:22 -04:00
parent d68deb9c74
commit 2f2a5e9b4a
158 changed files with 64811 additions and 503 deletions
+245
View File
@@ -0,0 +1,245 @@
# 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.
## 📍 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)
---
## 🔐 Authentification
Tous les endpoints (sauf `/health` et `/`) nécessitent une authentification via 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
```
> [!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.
---
## 🛡️ 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 | 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. |
---
## 📊 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.
| Plan | Uploads / heure | Requêtes AI / heure |
|------|-----------------|---------------------|
| `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
```bash
curl -X POST http://localhost:8000/auth/clients \
-H "Authorization: Bearer CLE_ADMIN" \
-H "Content-Type: application/json" \
-d '{
"name": "Application Mobile",
"scopes": ["images:read", "images:write", "ai:use"],
"plan": "standard"
}'
```
**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"
```
---
## 📁 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}/`).
---
## 📖 Référence des Endpoints
### 📸 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.
#### 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.
#### 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).
#### 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é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.
#### Retraitement
`POST /api/v1/images/{id}/reprocess`
- **Scope required** : `images:write`
- **Description** : Réinitialise et relance le pipeline d'analyse AI.
#### Suppression
`DELETE /api/v1/images/{id}`
- **Scope required** : `images:delete`
- **Description** : Supprime l'entrée en base et les fichiers sur le disque.
#### Tags globaux
`GET /api/v1/images/tags/all`
- **Scope required** : `images:read`
- **Description** : Liste tous les tags uniques utilisés par le client.
---
### 🤖 Intelligence Artificielle
#### 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)
```python
import httpx
headers = {"Authorization": "Bearer ma_super_cle"}
r = httpx.get("http://localhost:8000/images", headers=headers)
for img in r.json()["items"]:
print(f"ID: {img['id']} | Name: {img['original_name']}")
```
### Uploader une image (Node.js / Axios)
```javascript
const axios = require('axios');
const fs = require('fs');
const FormData = require('form-data');
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);
```
+60
View File
@@ -0,0 +1,60 @@
# Architecture Technique - Imago Hub
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.
---
## 🏗️ 1. Vue d'Ensemble des Composants
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.
---
## 🔄 2. Flux de Traitement Asynchrone (Le Pipeline)
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.
### 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é.
### 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).*
---
## 🗄️ 3. Modèle de Données & Multi-tenancy
La BDD relationnelle s'articule pour cloisonner (isolation de locataire) les données de façon logicielle :
- **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.
---
## 🔌 4. Observabilité et Temps Réel
- **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.
+59
View File
@@ -0,0 +1,59 @@
# Imago SDK - `imago-client`
The official Python SDK provides an asynchronous, strongly-typed interface to the Imago API.
## Installation
```bash
cd sdk
pip install -e .
```
## Basic Usage
The SDK provides an asynchronous Context Manager (`HubClient`) which handles connection pooling, retries, and authentication.
```python
import asyncio
from imago_client import HubClient
async def main():
async with HubClient("http://localhost:8000/api/v1", api_key="sk-test-secret-key") as client:
# 1. Upload an image
image = await client.images.upload("photo.jpg")
print(f"Uploaded! UUID: {image.uuid}")
# 2. Get status
details = await client.images.get(image.uuid)
print(f"Status: {details.processing_status}")
# 3. Use AI
summary = await client.ai.draft_task("https://example.com/article", "Summarize this quickly.")
print(summary)
if __name__ == "__main__":
asyncio.run(main())
```
## Advanced Features
### 1. Resilient Retries
By default, the SDK automatically retries requests on timeouts, TCP connection errors, `429 Too Many Requests`, and `5xx Server Errors`. It uses exponential backoff with jitter to ensure safe recovery.
### 2. Real-Time Pipeline Streaming
The SDK natively wraps the WebSockets API. You can `async for` over events until the pipeline finishes:
```python
async with HubClient(url, api_key) as client:
async for event in client.images.stream_pipeline_events("IMAGE_UUID"):
print(event["event"])
if event["event"] == "pipeline.done":
break
```
### 3. Error Handling
All specific cases throw custom exceptions inherited from `ImagoError`:
- `AuthError` (401)
- `QuotaError` (403 or 429 quota exhaustion)
- `NotFoundError` (404)
- `PipelineError` (WebSockets specific)
+45
View File
@@ -0,0 +1,45 @@
# Shaarli Integration
The **Imago Hub** can be integrated directly with **Shaarli** (the PHP-based bookmark manager) to automatically process and enhance your bookmarks when you save them.
## Overview
When you save a new bookmark in Shaarli, you might want to:
1. Automatically fetch the URL and summarize the page content using AI
2. Generate tags automatically based on the page content
3. Archive full-page screenshots to Imago for offline reading
This repository includes a reference implementation for this architecture using the Imago Python SDK.
## Components
The `integration/shaarli/` directory contains:
- `hub_plugin.py` — A Python class (`ShaarliHubPlugin`) that acts as a middleware interacting with the `imago-client` SDK.
- `config_example.env` — Example environment variable file for the plugin layer.
- `example_usage.py` — A standalone script demonstrating how the plugin summarizes URLs.
## Setup Instructions
1. **Install the SDK:** The script requires the Imago Python SDK.
```bash
cd sdk
pip install imago-client
```
2. **Configure Environment variables:**
Copy `integration/shaarli/config_example.env` to `.env` and fill it with your **Imago Admin API Key** or a specific Client API Key provisioned with `ai:use` and `images:write` scopes.
3. **Deploy as a microservice or hook:**
Since Shaarli is written in PHP, to hook python scripts into Shaarli you have a few options:
- **Option A (WebHook):** Deploy a small FastAPI/Flask wrapper around `hub_plugin.py` and configure Shaarli's WebHook plugin to POST data to your wrapper whenever a new link is created.
- **Option B (Cron/Background Job):** Have a Python daemon watch the Shaarli RSS/Atom feed or poll its REST API, grab new entries, process them via `hub_plugin.py`, and PATCH the bookmark back in Shaarli with the newly generated Description and Tags using the Shaarli REST API.
## Example Flow (Option B)
1. You save `https://github.com/imago-project/imago` to Shaarli.
2. The Python daemon detects the new bookmark via Shaarli's API.
3. The daemon calls `plugin.process_bookmark(url)`.
4. Imago leverages OpenRouter/Gemini to fetch and summarize the GitHub page.
5. Imago returns `{"title": "...", "description": "...", "suggested_tags": ["github", "python", "ai"]}`.
6. The daemon PATCHes the Shaarli API to enrich the bookmark.
+79
View File
@@ -0,0 +1,79 @@
# Guide d'Utilisation - Imago Hub
Bienvenue dans le guide d'utilisation d'**Imago Hub**. Ce document vous accompagnera dans l'utilisation des principales fonctionnalités de la plateforme, que vous interagissiez via l'API, le panel d'administration ou les SDKs.
---
## 📸 1. Gestion des Images
### Upload et Traitement Asynchrone
Imago est conçu pour ingérer rapidement des images et déléguer les traitements lourds (redimensionnement, IA, OCR) en arrière-plan (via les workers ARQ).
Lorsqu'une image est uploadée :
1. **Stockage immédiat** : L'image originale est sauvegardée localement (ou sur S3/MinIO).
2. **Création Miniature** : Une version allégée est générée.
3. **Mise en file d'attente** : L'image passe par un pipeline d'analyse :
- Extraction EXIF.
- Passage dans le moteur OCR.
- Analyse par la Vision IA.
### Suivre l'état d'un traitement
Étant donné que les IA (comme Gemini/OpenRouter) et l'OCR prennent du temps, l'upload retourne instantanément un `id` avec un statut `processing` ou `pending`.
Vous pouvez surveiller ce statut via :
- Le polling de l'endpoint : `GET /api/v1/images/{id}/status`
- Le streaming d'événements : Les applications frontend peuvent se connecter aux websockets d'Imago pour être notifiées en temps réel à chaque étape du pipeline. *(Voir [websocket.md](websocket.md))*
---
## 🤖 2. Intelligence Artificielle et OCR
### L'OCR (Reconnaissance Optique de Caractères)
Si activé, le moteur **Tesseract** parcourt chaque image pour en extraire le texte. Cela est particulièrement utile pour :
- Effectuer des recherches en texte intégral sur des captures d'écran, factures ou mémos photographiés.
- Lier ces données textuelles à l'image sans intervention humaine.
### Vision IA (Gemini / OpenRouter)
Imago est "AI-Native". Lorsqu'une image est traitée, elle est envoyée au fournisseur configuré (par exemple : `gemini-1.5-pro` ou `qwen2.5-vl` via OpenRouter).
L'IA réalise deux opérations clés :
1. **Description enrichie** : Elle rédige un résumé détaillé de la scène ou du document pour améliorer l'accessibilité et la compréhension.
2. **Génération de Tags** : Elle attribue entre 5 et 10 mots-clés (tags) pertinents qui seront indexés en base de données, rendant l'image facilement trouvable lors des recherches futures.
---
## 💻 3. Le Portail d'Administration
Le panel Admin (React) est accessible par défaut sur `http://localhost:3000`.
### Tableau de Bord
Le tableau de bord centralise toutes les images importées. Il permet de :
- Naviguer paginer parmi les milliers d'images gérées.
- Voir en un coup d'œil quelles images sont encore en cours de traitement, ont échoué ou sont validées.
- Filtrer par les Tags générés par l'IA.
### Visionneuse Détaillée
En cliquant sur une image, vous avez accès à une vue à 360° :
- L'image originale et sa miniature.
- **L'onglet "Sources & EXIF"** : Données de géolocalisation pour retracer le parcours de la photo, spécificités techniques (ouverture, ISO, modèle d'appareil).
- **L'onglet "Texte & IA"** : Le dictionnaire des termes générés par l'OCR et le résumé humain traduit.
### Gestion des Clients (Multi-tenancy)
*(Si vous possédez la permission `admin`)*
- Vous pouvez gérer les accès API directement depuis l'interface.
- Créer de nouveaux espaces (clients), générer des clés API et imputer des quotas (free, standard, premium) limitant le taux horaire de requêtes par tenant.
---
## 🔗 4. Interagir avec l'API
Tous les endpoints nécessitent une clé API valide (fournie par l'admin) transmise via le header d'Auth :
`Authorization: Bearer <votre_cle>`
Pour le catalogue exhaustif des endpoints (Upload, Recherche, AI standalone), référez-vous au :
👉 **[Guide détaillé de l'API (API_GUIDE.md)](API_GUIDE.md)**
### Résumé rapide des endpoints utiles :
| Action | Endpoint REST | Exemple d'Usage |
|--------|----------------------|-----------------|
| Envoyer | `POST /api/v1/images/upload` | Intégrer depuis votre app mobile. |
| Chercher| `GET /api/v1/images?search=voiture` | Trouver une image via l'OCR ou les tags IA. |
| Santé | `GET /health/detailed` | Surveiller l'état de Redis, DB, et MinIO. |
+33
View File
@@ -0,0 +1,33 @@
# WebSockets & Real-Time Events
Imago uses WebSockets and Redis Pub/Sub to provide real-time updates for asynchronous tasks (e.g. image OCR, EXIF extraction, AI summarization).
## Endpoints
All WebSockets endpoints wait for events from the `arq` queue. They require an authentication token to be passed via the `token` query parameter.
### 1. Per-Image Pipeline Events
**Endpoint:** `ws://localhost:8000/ws/pipeline/{image_id}?token=YOUR_API_KEY`
This endpoint streams events related to a specific image. If the image is already fully processed when you connect, the server will immediately yield an artificial `{"event": "pipeline.done", "status": "completed"}` event and the connection may close, ensuring you never miss the final state.
**Required Scopes:** `images:read` AND the image must belong to the client (unless the client is an Admin).
#### Example Events
```json
{"event": "pipeline.started"}
{"event": "task.exif.started"}
{"event": "task.exif.done"}
{"event": "pipeline.done", "status": "completed"}
```
### 2. Global Admin Monitor
**Endpoint:** `ws://localhost:8000/ws/admin/monitor?token=YOUR_ADMIN_KEY`
This endpoint streams high-level pipeline events (start, done, error) for ALL images across the system. It is meant for admin dashboards to display live activity feeds.
**Required Scopes:** `admin`
## Event Buffering
If your client disconnects and reconnects briefly, it might miss events. Imago buffers the last 10 events for each active image pipeline in Redis with a 60-second TTL. When a client connects to the per-image endpoint, any buffered events that were recorded before the WebSocket fully opened are immediately flushed down the socket, enabling seamless UI updates.