# Imago Backend FastAPI pour la gestion d'images et fonctionnalités AI, conçu comme complément à l'interface Web-thème **Shaarli-Pro** et toute autre applications qui voudrait l'utiliser. --- ## Fonctionnalités | Feature | Description | |---|---| | 📸 Upload d'images | Stockage + thumbnails, isolés par client (multi-tenant) | | 🛡️ Sécurité | Authentification par API Key + scopes granulaires + Rate Limiting | | 🔍 Extraction EXIF | Appareil photo, GPS, ISO, ouverture, date de prise de vue | | 📝 OCR | Extraction du texte visible dans l'image (Tesseract + fallback AI) | | 🤖 Vision AI | Description naturelle + classification par tags (Gemini / OpenRouter) | | 🔗 Résumé URL | Scraping d'une page web + résumé AI | | ✅ Rédaction de tâches | Génération structurée d'une tâche à partir d'une description | | 📋 File de tâches ARQ | Pipeline persistant avec retry automatique (Redis) | | 📊 Métriques Prometheus | Endpoint `/metrics` + custom counters/histograms | | 💾 Storage abstrait | Local (HMAC signed URLs) ou S3/MinIO (presigned URLs) | | 📈 Quota tracking | Suivi du stockage par client avec enforcement | | 🔧 Logging structuré | JSON (production) / Console colorée (dev) via structlog | --- ## Stack technique ``` FastAPI + Uvicorn — Framework web async SQLAlchemy (async) — ORM + SQLite/Postgres ARQ + Redis — File de tâches persistante Pillow + piexif — Traitement d'images + EXIF pytesseract — OCR (Tesseract) Google GenAI SDK — Vision AI (Gemini) aioboto3 — Stockage S3/MinIO structlog — Logging structuré JSON Prometheus — Métriques et monitoring slowapi — Rate limiting par client ruff — Linting + formatting ``` --- ## Installation ### Prérequis - Python 3.12+ (3.14 supporté) - Tesseract OCR installé sur le système - Redis (optionnel, pour ARQ worker) ```bash # Ubuntu/Debian sudo apt install tesseract-ocr tesseract-ocr-fra tesseract-ocr-eng # macOS brew install tesseract tesseract-lang # Windows scoop install main/tesseract ``` ### Setup > [!NOTE] > Les fichiers `requirements.txt` appliquent automatiquement des versions compatibles Python 3.14 (notamment pour Pillow et Pydantic). ```bash # 1. Cloner et installer git clone cd imago # Production pip install -r requirements.txt # Développement (inclut linting, tests, pre-commit) pip install -r requirements-dev.txt pre-commit install # 2. Configuration cp .env.example .env # Éditer .env — voir la section Configuration ci-dessous # 3. Migrations alembic upgrade head # La migration crée un client "default" et affiche sa clé API une seule fois. # 4. Démarrage python run.py # API sur http://localhost:8000 python worker.py # Worker ARQ (requiert Redis) ``` ### Avec Docker ```bash docker-compose up -d # API + Redis + Worker + PostgreSQL ``` ### Commandes utiles (Makefile) ```bash make help # Affiche toutes les commandes make install-dev # Dépendances dev + pre-commit make lint # Ruff + Mypy make format # Auto-format du code make test # Tests rapides make test-cov # Tests + couverture HTML make serve # Serveur dev (reload) make worker # Worker ARQ make docker-up # Docker compose up make clean # Nettoyage __pycache__, .pytest_cache, etc. ``` --- ## Pipeline de traitement AI Chaque image uploadée déclenche un pipeline via ARQ (Redis) : ``` Upload → Sauvegarde fichier + thumbnail → BDD (PENDING) │ ▼ (ARQ Worker — persistant, avec retry) ┌────────────────────────────────────────┐ │ Étape 1 — Extraction EXIF │ │ Étape 2 — OCR (Tesseract + AI) │ │ Étape 3 — Vision AI (Gemini) │ └────────────────────────────────────────┘ │ ▼ BDD (DONE) + événements Redis pub/sub ``` > [!IMPORTANT] > **Isolation multi-tenant** : fichiers et données sont compartimentés par `client_id`. Un client ne peut ni voir, ni modifier, ni supprimer les images d'un autre. Chaque étape est **indépendante** : un échec partiel n'arrête pas le pipeline. **Priority queues** : les clients `premium` ont une file dédiée. **Retry automatique** avec backoff exponentiel. Dead-letter après max retries. --- ## Endpoints API ### Images (Auth requise) | Méthode | URL | Description | Scope | |---|---|---|---| | `POST` | `/images/upload` | Uploader une image | `images:write` | | `GET` | `/images` | Lister (paginé, filtrable, quota info) | `images:read` | | `GET` | `/images/{id}` | Détail complet | `images:read` | | `GET` | `/images/{id}/status` | Statut du pipeline | `images:read` | | `GET` | `/images/{id}/exif` | Métadonnées EXIF | `images:read` | | `GET` | `/images/{id}/ocr` | Texte OCR extrait | `images:read` | | `GET` | `/images/{id}/ai` | Description + tags AI | `images:read` | | `GET` | `/images/{id}/download-url` | URL signée de téléchargement | `images:read` | | `GET` | `/images/{id}/thumbnail-url` | URL signée du thumbnail | `images:read` | | `GET` | `/images/tags/all` | Tous les tags du client | `images:read` | | `POST` | `/images/{id}/reprocess` | Relancer le pipeline | `images:write` | | `DELETE` | `/images/{id}` | Supprimer image | `images:delete` | ### Intelligence Artificielle (Auth requise) | Méthode | URL | Description | Scope | |---|---|---|---| | `POST` | `/ai/summarize` | Résumé AI d'une URL | `ai:use` | | `POST` | `/ai/draft-task` | Rédaction de tâche | `ai:use` | ### Administration & Clients (Admin only) | Méthode | URL | Description | Scope | |---|---|---|---| | `POST` | `/auth/clients` | Créer un client API | `admin` | | `GET` | `/auth/clients` | Lister les clients | `admin` | | `GET` | `/auth/clients/{id}` | Détail d'un client | `admin` | | `PATCH` | `/auth/clients/{id}` | Modifier client (plan, scopes) | `admin` | | `POST` | `/auth/clients/{id}/rotate-key` | Régénérer clé API | `admin` | | `DELETE` | `/auth/clients/{id}` | Soft delete (désactivation) | `admin` | ### Fichiers signés | Méthode | URL | Description | Auth | |---|---|---|---| | `GET` | `/files/signed/{token}` | Télécharger via URL signée | Token HMAC | ### Observabilité & Santé | Méthode | URL | Description | Auth | |---|---|---|---| | `GET` | `/` | Info application | Non | | `GET` | `/health` | Santé du service | Non | | `GET` | `/health/detailed` | Santé détaillée (DB, Redis, ARQ, OCR) | Non | | `GET` | `/metrics` | Métriques Prometheus | Non | | `GET` | `/docs` | Documentation Swagger | Non | --- ## Exemples d'utilisation > [!TIP] > Tous les appels (sauf `/health` et `/metrics`) nécessitent une clé API valide passée dans le header `X-API-Key`. ### Upload d'une image ### api_key=emH92l92LD4L7cLhl2imidMZANsIUb9x_AlGWiYpVSA client_id=925463e0-27a4-4993-aa3a-f1cb31c19d32 warning=Notez cette clé ! Elle ne sera plus affichée. ```bash curl -X POST http://localhost:8000/images/upload \ -H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg" \ -F "file=@pushup.gif" ``` Réponse : ```json { "id": 1, "uuid": "a1b2c3d4-...", "original_name": "photo.jpg", "status": "pending", "message": "Image uploadée — traitement AI en cours" } ``` ### Polling du statut ```bash curl http://localhost:8000/images/1/status -H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg" ``` ```json { "id": 1, "status": "done", "started_at": "2024-01-15T10:30:00", "done_at": "2024-01-15T10:30:08" } ``` ### Détail complet ```bash curl http://localhost:8000/images/1 -H "X-API-Key: rEYQtw3LxJJlcmBq-cgQcdeY74JcpJ45COuFWokmxPg" ``` ```json { "id": 1, "original_name": "photo.jpg", "processing_status": "done", "exif": { "camera": { "make": "Canon", "model": "EOS R5", "iso": 400, "aperture": "f/2.8", "shutter_speed": "1/250", "focal_length": "50mm", "taken_at": "2024-06-15T14:30:00" }, "gps": { "latitude": 48.8566, "longitude": 2.3522, "has_gps": true, "maps_url": "https://maps.google.com/?q=48.8566,2.3522" } }, "ocr": { "has_text": true, "text": "Café de Flore", "language": "fr", "confidence": 0.94 }, "ai": { "description": "Une terrasse de café parisien animée en fin d'après-midi. Les tables en osier sont disposées sur le trottoir boulevard Saint-Germain, avec une clientèle détendue profitant du soleil. L'enseigne emblématique du Café de Flore est visible en arrière-plan.", "tags": ["café", "paris", "terrasse", "france", "bistrot", "extérieur", "urbain"], "confidence": 0.97, "model_used": "gemini-1.5-pro" } } ``` ### Résumé d'URL ```bash curl -X POST http://localhost:8000/ai/summarize \ -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/article", "language": "français"}' ``` ### Rédaction de tâche ```bash curl -X POST http://localhost:8000/ai/draft-task \ -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{ "description": "Mettre à jour la documentation technique du projet backend", "context": "Projet Imago, backend FastAPI" }' ``` --- ## Configuration | Variable | Défaut | Description | |---|---|---| | `ADMIN_API_KEY` | — | Clé maîtresse pour gérer les clients API | | `JWT_SECRET_KEY` | — | Secret pour la signature des tokens | | `AI_PROVIDER` | `gemini` | `gemini` ou `openrouter` | | `GEMINI_API_KEY` | — | Clé API Gemini | | `DATABASE_URL` | PostgreSQL (Docker) / SQLite (Local) | URL de connexion (Postgres recommandé) | | `REDIS_URL` | `redis://localhost:6379/0` | URL Redis pour ARQ | | `STORAGE_BACKEND` | `local` | `local` ou `s3` | | `S3_BUCKET` | — | Bucket S3/MinIO | | `S3_ENDPOINT_URL` | — | Endpoint S3 custom (MinIO/R2) | | `S3_ACCESS_KEY` | — | Clé d'accès S3 | | `S3_SECRET_KEY` | — | Secret S3 | | `SIGNED_URL_SECRET` | — | Secret HMAC pour URLs signées locales | | `MAX_UPLOAD_SIZE_MB` | `50` | Taille max par upload | | `OCR_ENABLED` | `true` | Activer/désactiver l'OCR | | `WORKER_MAX_JOBS` | `10` | Concurrence du worker ARQ | | `WORKER_MAX_TRIES` | `3` | Retries max avant dead-letter | | `DEBUG` | `false` | Mode debug (console logging) | --- ## Tests ```bash # Tests rapides make test # Tests avec couverture make test-cov # Directement python -m pytest tests/ -v --cov=app ``` 76 tests couvrant : auth, isolation multi-tenant, rate limiting, pipeline, EXIF, OCR, AI, stockage. --- ## CI/CD Le pipeline GitHub Actions (`.github/workflows/ci.yml`) exécute : 1. **Lint** — ruff check + format + mypy 2. **Tests** — pytest + couverture 3. **Sécurité** — bandit via ruff (règles S) 4. **Docker** — build + smoke test (branche main uniquement) --- ## Structure du projet ``` imago/ ├── app/ │ ├── main.py # FastAPI + lifespan + Prometheus │ ├── config.py # Settings Pydantic │ ├── database.py # Engine SQLAlchemy async │ ├── logging_config.py # structlog JSON/Console │ ├── metrics.py # Prometheus custom metrics │ ├── models/ │ │ ├── image.py # Modèle Image │ │ └── client.py # APIClient + quotas │ ├── schemas/ │ │ ├── __init__.py # Schémas images + quota │ │ └── auth.py # Schémas Auth/Clients │ ├── routers/ │ │ ├── images.py # CRUD images + signed URLs │ │ ├── ai.py # Endpoints AI │ │ ├── auth.py # Gestion clients │ │ └── files.py # Serving signed local files │ ├── dependencies/ │ │ └── auth.py # verify_api_key, require_scope │ ├── middleware/ │ │ ├── rate_limit.py # slowapi configuration │ │ └── logging_middleware.py # HTTP request logger │ ├── services/ │ │ ├── storage.py # Sauvegarde isolée par client │ │ ├── storage_backend.py # ABC + Local/S3 backends │ │ ├── pipeline.py # Pipeline EXIF → OCR → AI │ │ ├── ai_vision.py # Gemini/OpenRouter │ │ ├── exif_service.py # Extraction EXIF │ │ ├── ocr_service.py # Tesseract OCR │ │ └── scraper.py # Scraping web │ └── workers/ │ ├── redis_client.py # Redis pool partagé │ └── image_worker.py # ARQ worker + retries ├── tests/ # 76 tests ├── .github/workflows/ci.yml # CI/CD pipeline ├── pyproject.toml # ruff, mypy, coverage config ├── Makefile # Commandes utiles ├── docker-compose.yml # API + Redis + Worker + PostgreSQL ├── Dockerfile ├── requirements.txt # Production deps ├── requirements-dev.txt # Dev deps (lint, test) ├── .pre-commit-config.yaml # Pre-commit hooks ├── worker.py # ARQ worker entrypoint └── .env.example ```