Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
45 KiB
Story 3.6: Documentation OpenAPI (Swagger + ReDoc)
Status: done
Story
En tant que Développeur/API User, Je veux avoir une documentation API interactive et complète, de sorte que je puisse comprendre et tester tous les endpoints de l'API facilement.
Acceptance Criteria
- Swagger UI Accessible: GET
/docsaffiche Swagger UI avec tous les endpoints. (FR35, NFR18) - ReDoc Accessible: GET
/redocaffiche ReDoc avec documentation claire et lisible. - Schémas Complets: Tous les request/response schemas sont documentés avec types et descriptions.
- Authentification Documentée: Méthodes JWT et API Key sont documentées dans OpenAPI avec exemples.
- Codes Erreur Documentés: Tous les codes d'erreur sont listés avec exemples JSON.
- Version API: La version (v1) est clairement visible dans la documentation.
- Try It Out: Swagger UI permet de tester les endpoints directement depuis l'interface.
Tasks / Subtasks
-
Task 1: Configurer FastAPI pour OpenAPI (AC: #1, #2, #6)
- 1.1 Vérifier que FastAPI génère OpenAPI 3.0 automatiquement
- 1.2 Configurer les URLs
/docs(Swagger UI) et/redoc(ReDoc) - 1.3 Personnaliser le titre, description et version dans
FastAPI() - 1.4 Ajouter les informations de contact et licence
- 1.5 Configurer les tags pour grouper les endpoints
-
Task 2: Documenter l'Authentification (AC: #4)
- 2.1 Configurer
HTTPBearersecurity scheme pour JWT - 2.2 Configurer
APIKeyHeadersecurity scheme pour X-API-Key - 2.3 Ajouter des exemples d'authentification dans la documentation
- 2.4 Documenter comment obtenir un token JWT et une API key
- 2.1 Configurer
-
Task 3: Documenter les Endpoints de Translation (AC: #3, #5)
- 3.1 Documenter POST
/api/v1/translateavec tous les paramètres - 3.2 Documenter GET
/api/v1/translations/{id}pour le statut - 3.3 Documenter GET
/api/v1/download/{id}pour le téléchargement - 3.4 Documenter GET
/api/v1/languagespour les langues supportées - 3.5 Ajouter des exemples de request/response pour chaque endpoint
- 3.6 Documenter tous les codes d'erreur possibles
- 3.1 Documenter POST
-
Task 4: Documenter les Endpoints Auth (AC: #3, #5)
- 4.1 Documenter POST
/api/v1/auth/register - 4.2 Documenter POST
/api/v1/auth/login - 4.3 Documenter POST
/api/v1/auth/logout - 4.4 Documenter POST
/api/v1/auth/refresh - 4.5 Ajouter exemples et erreurs pour chaque endpoint
- 4.1 Documenter POST
-
Task 5: Documenter les Endpoints API Keys (AC: #3, #5)
- 5.1 Documenter POST
/api/v1/api-keys(génération) - 5.2 Documenter GET
/api/v1/api-keys(liste) - 5.3 Documenter DELETE
/api/v1/api-keys/{key_id}(révocation) - 5.4 Documenter DELETE
/api/v1/admin/api-keys/{key_id}(admin révocation)
- 5.1 Documenter POST
-
Task 6: Documenter les Endpoints Admin (AC: #3, #5)
- 6.1 Documenter POST
/api/v1/admin/login - 6.2 Documenter POST
/api/v1/admin/logout - 6.3 Documenter GET
/api/v1/admin/dashboard - 6.4 Documenter GET
/api/v1/admin/users - 6.5 Documenter PATCH
/api/v1/admin/users/{user_id} - 6.6 Documenter GET
/api/v1/admin/stats - 6.7 Documenter POST
/api/v1/admin/cleanup/trigger - 6.8 Documenter GET
/api/v1/admin/files/tracked - 6.9 Documenter POST
/api/v1/admin/config/provider
- 6.1 Documenter POST
-
Task 7: Documenter les Endpoints Legacy/Misc (AC: #3, #5)
- 7.1 Documenter GET
/api/v1/languages - 7.2 Documenter POST
/api/v1/translate-batch - 7.3 Documenter POST
/api/v1/extract-texts - 7.4 Documenter POST
/api/v1/reconstruct-document - 7.5 Documenter GET
/api/v1/ollama/models - 7.6 Documenter POST
/api/v1/ollama/configure - 7.7 Documenter GET
/api/v1/metrics - 7.8 Documenter GET
/api/v1/rate-limit/status - 7.9 Documenter DELETE
/api/v1/cleanup/{filename}
- 7.1 Documenter GET
-
Task 8: Créer des Schémas Pydantic pour la Documentation (AC: #3)
- 8.1 Créer des modèles de request (TranslateRequest, LoginRequest, etc.)
- 8.2 Créer des modèles de response (TranslateResponse, ErrorResponse, etc.)
- 8.3 Créer des modèles d'erreur avec tous les codes
- 8.4 Ajouter des descriptions et exemples dans chaque champ Pydantic
-
Task 9: Ajouter des Exemples Complets (AC: #5, #7)
- 9.1 Ajouter des examples de requests complètes pour chaque endpoint
- 9.2 Ajouter des examples de responses succès pour chaque endpoint
- 9.3 Ajouter des examples d'erreurs pour chaque code d'erreur
- 9.4 Configurer les exemples dans OpenAPI avec
examplesparameter
-
Task 10: Tester la Documentation (AC: Tous)
- 10.1 Tester que
/docsaffiche Swagger UI correctement - 10.2 Tester que
/redocaffiche ReDoc correctement - 10.3 Vérifier que tous les endpoints sont listés
- 10.4 Vérifier que les schémas sont complets et corrects
- 10.5 Vérifier que les exemples sont affichés
- 10.6 Tester "Try It Out" sur différents endpoints
- 10.7 Vérifier que l'authentification fonctionne dans Swagger UI
- 10.1 Tester que
Dev Notes
État Actuel de la Documentation
Documentation EXISTANTE (FastAPI génère automatiquement):
/docs- Swagger UI (par défaut) ✅/redoc- ReDoc (par défaut) ✅/openapi.json- Spec OpenAPI brute ✅
CE QUI MANQUE:
- ❌ Descriptions complètes pour tous les endpoints
- ❌ Exemples de request/response
- ❌ Documentation des codes d'erreur
- ❌ Documentation des schémas d'authentification
- ❌ Modèles Pydantic pour toutes les requests/responses
- ❌ Exemples concrets pour tester depuis Swagger UI
Architecture OpenAPI FastAPI
FastAPI génère automatiquement OpenAPI 3.0, mais nécessite configuration:
# main.py - Configuration de base
from fastapi import FastAPI
app = FastAPI(
title="Office Translator API",
version="1.0.0",
description="API de traduction de documents Office (Excel, Word, PowerPoint)",
docs_url="/docs", # Swagger UI
redoc_url="/redoc", # ReDoc
openapi_url="/openapi.json" # OpenAPI spec
)
Configuration AVANCÉE recommandée:
# main.py
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Office Translator API",
version="1.0.0",
description="""
API de traduction de documents Office avec préservation du format.
## Authentification
L'API supporte deux méthodes d'authentification:
### 1. JWT (Web Dashboard)
- Obtenez un token via `/api/v1/auth/login`
- Utilisez le header: `Authorization: Bearer <token>`
- Token expire en 15 minutes (access) ou 7 jours (refresh)
### 2. API Key (Automation)
- Générez une clé via `/api/v1/api-keys` (Pro users only)
- Utilisez le header: `X-API-Key: sk_live_xxx`
- Clé statique, révocable
## Endpoints Principaux
- **Translation**: POST /api/v1/translate
- **Status**: GET /api/v1/translations/{id}
- **Download**: GET /api/v1/download/{id}
- **Languages**: GET /api/v1/languages
## Codes d'Erreur
Tous les codes d'erreur suivent le format:
```json
{
"error": "ERROR_CODE",
"message": "Description lisible",
"details": {...}
}
Codes courants: INVALID_FORMAT, QUOTA_EXCEEDED, UNAUTHORIZED, PROVIDER_ERROR
""",
routes=app.routes,
)
# Configuration des security schemes
openapi_schema["components"]["securitySchemes"] = {
"JWT": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT token from /api/v1/auth/login"
},
"APIKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API Key from /api/v1/api-keys (Pro only)"
}
}
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
### Patterns de Documentation d'Endpoints
**Pattern avec Pydantic models**:
```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, Literal
class TranslateRequest(BaseModel):
"""Request model for document translation"""
source_lang: str = Field(
...,
example="en",
description="Source language code (ISO 639-1)"
)
target_lang: str = Field(
...,
example="fr",
description="Target language code (ISO 639-1)"
)
mode: Literal["classic", "llm"] = Field(
default="classic",
example="classic",
description="Translation mode: 'classic' (Google/DeepL) or 'llm' (Ollama/OpenAI)"
)
provider: Optional[str] = Field(
None,
example="google",
description="Specific provider to use (optional, uses fallback chain if not specified)"
)
webhook_url: Optional[str] = Field(
None,
example="https://example.com/webhook",
description="URL to receive POST notification when translation completes (optional)"
)
class TranslateResponse(BaseModel):
"""Response model for translation request"""
id: str = Field(..., example="tr_abc123", description="Translation job ID")
status: str = Field(..., example="processing", description="Job status: queued, processing, completed, failed")
file_name: str = Field(..., example="report.xlsx", description="Original file name")
class Config:
schema_extra = {
"example": {
"id": "tr_abc123",
"status": "processing",
"file_name": "report.xlsx"
}
}
class ErrorResponse(BaseModel):
"""Error response model"""
error: str = Field(..., example="INVALID_FORMAT", description="Error code")
message: str = Field(..., example="Format PDF non supporté", description="Human-readable error message")
details: Optional[dict] = Field(None, example={"accepted_formats": [".xlsx", ".docx", ".pptx"]})
@app.post(
"/api/v1/translate",
response_model=TranslateResponse,
status_code=202,
summary="Translate a document",
description="Upload a document for translation. Supported formats: .xlsx, .docx, .pptx",
responses={
202: {
"description": "Translation started",
"content": {
"application/json": {
"example": {
"id": "tr_abc123",
"status": "processing",
"file_name": "report.xlsx"
}
}
}
},
400: {
"model": ErrorResponse,
"description": "Invalid request",
"content": {
"application/json": {
"examples": {
"INVALID_FORMAT": {
"summary": "Unsupported file format",
"value": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {"accepted_formats": [".xlsx", ".docx", ".pptx"]}
}
},
"FILE_TOO_LARGE": {
"summary": "File exceeds size limit",
"value": {
"error": "FILE_TOO_LARGE",
"message": "Le fichier dépasse la limite de 50 MB",
"details": {"max_size_mb": 50, "actual_size_mb": 65}
}
}
}
}
}
},
401: {"model": ErrorResponse, "description": "Authentication required"},
429: {"model": ErrorResponse, "description": "Quota exceeded"},
}
)
async def translate_document(
file: UploadFile = File(..., description="Document to translate (.xlsx, .docx, .pptx)"),
request: TranslateRequest = Depends()
):
"""
Translate a document while preserving formatting.
- **file**: Document to translate (xlsx, docx, pptx only, max 50MB)
- **source_lang**: Source language code
- **target_lang**: Target language code
- **mode**: Translation mode (classic or llm, Pro only for llm)
- **provider**: Specific provider to use (optional)
- **webhook_url**: URL for completion notification (optional)
Returns job ID for tracking progress.
"""
pass
Structure de Fichiers pour Documentation
schemas/
├── __init__.py
├── translation.py # TranslateRequest, TranslateResponse, etc.
├── auth.py # LoginRequest, RegisterRequest, TokenResponse, etc.
├── api_keys.py # APIKeyCreate, APIKeyResponse, etc.
├── admin.py # AdminDashboard, UserStats, etc.
├── errors.py # ErrorResponse, ErrorCodes enum, etc.
└── common.py # PaginationMeta, HealthCheck, etc.
routes/
├── translate_routes.py # Endpoints avec docstrings et responses dict
├── auth_routes.py
├── api_key_routes.py
├── admin_routes.py
└── legacy_routes.py
main.py # Configuration OpenAPI custom
Documentation des Codes d'Erreur
Fichier schemas/errors.py:
from enum import Enum
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
class ErrorCode(str, Enum):
"""All error codes used in the API"""
INVALID_FORMAT = "INVALID_FORMAT"
QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
UNAUTHORIZED = "UNAUTHORIZED"
FORBIDDEN = "FORBIDDEN"
FILE_TOO_LARGE = "FILE_TOO_LARGE"
PROVIDER_ERROR = "PROVIDER_ERROR"
EMAIL_EXISTS = "EMAIL_EXISTS"
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
USER_NOT_FOUND = "USER_NOT_FOUND"
API_KEY_REVOKED = "API_KEY_REVOKED"
PRO_FEATURE_REQUIRED = "PRO_FEATURE_REQUIRED"
GLOSSARY_NOT_FOUND = "GLOSSARY_NOT_FOUND"
PROMPT_NOT_FOUND = "PROMPT_NOT_FOUND"
WEBHOOK_FAILED = "WEBHOOK_FAILED"
INTERNAL_ERROR = "INTERNAL_ERROR"
class ErrorResponse(BaseModel):
"""Standard error response format"""
error: ErrorCode = Field(..., description="Error code")
message: str = Field(..., description="Human-readable error message in French")
details: Optional[Dict[str, Any]] = Field(None, description="Additional error context")
class Config:
schema_extra = {
"example": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {"accepted_formats": [".xlsx", ".docx", ".pptx"]}
}
}
Project Structure Notes
- Le projet suit une structure plate (pas de dossier
backend/app/) - Les routeurs sont dans
routes/ - Les modèles Pydantic pour la documentation seront dans
schemas/(à créer) - Les tests sont dans
tests/
Références
- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.6]
- [Source: _bmad-output/planning-artifacts/architecture.md#API & Communication Patterns]
- [Source: _bmad-output/planning-artifacts/architecture.md#API Response Formats]
- [Source: FastAPI Documentation - https://fastapi.tiangolo.com/tutorial/metadata/]
- [Source: FastAPI Documentation - https://fastapi.tiangolo.com/tutorial/security/]
Intelligence des Stories Précédentes (Epic 3)
Story 3.1 (API Key Generation) - Enseignements
- Routeur api_key_routes.py utilise
prefix="/api/v1/api-keys"✅ - Pattern de réponse
{data: {...}, meta: {...}}établi - Tests complets avec fixtures dans
tests/conftest.py
Story 3.2 (API Key Revocation User) - Enseignements
- Soft delete avec
is_active=False - Fonction
get_user_by_api_keydansservices/auth_service.py - Format d'erreur structuré
{error, message, details?}
Story 3.3 (Admin API Key Revocation) - Enseignements
- Routes admin dans main.py - à migrer vers
routes/admin_routes.py - Dépendance
require_adminpour l'authentification admin - Audit logging avec
logger.info()
Story 3.4 (API Auth X-API-Key) - Enseignements
- Coexistence JWT + API Key dans
get_authenticated_user - Middleware d'auth dans
routes/translate_routes.py - Priorité API key sur JWT si les deux présents
Story 3.5 (API Versioning) - Enseignements
- Tous les endpoints sont maintenant sous
/api/v1/✅ - Exceptions documentées:
/health,/ready,/docs,/redocaccessibles sans préfixe - Routeur principal
routes/api_v1_router.pycomposé de sous-routeurs - Configuration FastAPI avec
title,version,descriptiondans main.py
Intelligence Git (Commits Récents)
Derniers commits pertinents:
3d37ce4: PostgreSQL database infrastructure550f351: Database migrations avec Alembicc4d6cae: Security hardening, Redis sessions
Patterns identifiés:
- Tests avec
pytestdanstests/ - Fixtures dans
tests/conftest.py - Configuration via
config.pyet variables d'environnement
Contexte Métier
Epic 3: API & Automation (Pro)
Cette story est la sixième de l'Epic 3 qui permet aux utilisateurs Pro d'automatiser les traductions:
API Keys - Génération(Story 3.1 ✅)API Keys - Révocation User(Story 3.2 ✅)API Keys - Révocation Admin(Story 3.3 ✅)Authentification X-API-Key(Story 3.4 ✅)API Versioning(Story 3.5 ✅)- Documentation OpenAPI (cette story)
- Webhooks (Stories 3.7-3.8 - backlog)
- Glossaires (Stories 3.9-3.10 - backlog)
- Custom Prompts (Stories 3.11-3.12 - backlog)
Valeur Business
La documentation OpenAPI est critique pour:
- Adoption: Thomas (Pro user) peut explorer l'API sans lire de doc externe
- Testabilité: Swagger UI permet de tester directement dans le navigateur
- Intégration: Les clients peuvent générer du code client depuis la spec OpenAPI
- Maintenance: La documentation est toujours à jour (générée depuis le code)
- Professionalisme: Standard de l'industrie pour les APIs REST
Dépendances
- Stories 3.1-3.5 (prérequis): Tous les endpoints doivent exister et être versionnés ✅
- Stories futures (impact): Webhooks, Glossaires, Custom Prompts devront être documentés
Guardrails Développeur
❌ À NE PAS FAIRE
- NE PAS supprimer ou déplacer
/docs,/redoc,/openapi.json - NE PAS utiliser des docstrings vides ou génériques
- NE PAS oublier de documenter les codes d'erreur
- NE PAS créer des modèles Pydantic incomplets (tous les champs doivent avoir des descriptions et exemples)
- NE PAS ignorer les exemples de request/response
- NE PAS oublier de documenter l'authentification (JWT + API Key)
✅ À FAIRE
- TOUJOURS ajouter des descriptions complètes dans les endpoints
- TOUJOURS créer des modèles Pydantic pour toutes les requests/responses
- TOUJOURS documenter TOUS les codes d'erreur possibles
- TOUJOURS ajouter des exemples concrets (example parameter dans Field)
- TOUJOURS utiliser le dict
responses={}pour documenter les erreurs HTTP - CRÉER un fichier
schemas/errors.pyavec tous les codes d'erreur - CRÉER un fichier
schemas/pour organiser les modèles Pydantic - CONFIGURER
custom_openapi()dans main.py pour la documentation personnalisée
Code Suggéré
Fichier schemas/__init__.py à créer
"""Pydantic models for API documentation and validation"""
from .translation import (
TranslateRequest,
TranslateResponse,
TranslationStatusResponse,
LanguageResponse,
)
from .auth import (
RegisterRequest,
LoginRequest,
TokenResponse,
LogoutResponse,
RefreshRequest,
)
from .api_keys import (
APIKeyCreate,
APIKeyResponse,
APIKeyListResponse,
)
from .admin import (
AdminLoginRequest,
AdminDashboardResponse,
AdminUserResponse,
AdminUserUpdateRequest,
AdminStatsResponse,
)
from .errors import ErrorResponse, ErrorCode
from .common import (
SuccessResponse,
PaginationMeta,
HealthCheckResponse,
)
__all__ = [
# Translation
"TranslateRequest",
"TranslateResponse",
"TranslationStatusResponse",
"LanguageResponse",
# Auth
"RegisterRequest",
"LoginRequest",
"TokenResponse",
"LogoutResponse",
"RefreshRequest",
# API Keys
"APIKeyCreate",
"APIKeyResponse",
"APIKeyListResponse",
# Admin
"AdminLoginRequest",
"AdminDashboardResponse",
"AdminUserResponse",
"AdminUserUpdateRequest",
"AdminStatsResponse",
# Errors
"ErrorResponse",
"ErrorCode",
# Common
"SuccessResponse",
"PaginationMeta",
"HealthCheckResponse",
]
Fichier schemas/translation.py à créer
"""Pydantic models for translation endpoints"""
from pydantic import BaseModel, Field
from typing import Optional, Literal, List
from datetime import datetime
class TranslateRequest(BaseModel):
"""Request model for document translation"""
source_lang: str = Field(
...,
example="en",
description="Source language code (ISO 639-1)",
min_length=2,
max_length=3
)
target_lang: str = Field(
...,
example="fr",
description="Target language code (ISO 639-1)",
min_length=2,
max_length=3
)
mode: Literal["classic", "llm"] = Field(
default="classic",
example="classic",
description="Translation mode: 'classic' (Google/DeepL) or 'llm' (Ollama/OpenAI)"
)
provider: Optional[str] = Field(
None,
example="google",
description="Specific provider to use (optional, uses fallback chain if not specified)"
)
webhook_url: Optional[str] = Field(
None,
example="https://example.com/webhook",
description="URL to receive POST notification when translation completes (optional)"
)
glossary_id: Optional[str] = Field(
None,
example="gloss_abc123",
description="Glossary ID to apply during LLM translation (Pro only, optional)"
)
prompt_id: Optional[str] = Field(
None,
example="prompt_xyz789",
description="Custom prompt ID to use for LLM translation (Pro only, optional)"
)
class TranslateResponse(BaseModel):
"""Response model for translation request"""
id: str = Field(..., example="tr_abc123", description="Translation job ID")
status: str = Field(..., example="processing", description="Job status: queued, processing, completed, failed")
file_name: str = Field(..., example="report.xlsx", description="Original file name")
source_lang: str = Field(..., example="en", description="Source language")
target_lang: str = Field(..., example="fr", description="Target language")
created_at: datetime = Field(..., example="2024-01-15T10:30:00Z", description="Creation timestamp")
class Config:
schema_extra = {
"example": {
"id": "tr_abc123",
"status": "processing",
"file_name": "report.xlsx",
"source_lang": "en",
"target_lang": "fr",
"created_at": "2024-01-15T10:30:00Z"
}
}
class TranslationStatusResponse(BaseModel):
"""Response model for translation status check"""
id: str = Field(..., example="tr_abc123")
status: str = Field(..., example="processing", description="queued, processing, completed, failed")
progress_percent: int = Field(..., example=65, description="Progress percentage (0-100)")
current_step: str = Field(..., example="Translating slide 3/5", description="Current processing step")
error_message: Optional[str] = Field(None, example="Provider timeout", description="Error message if failed")
created_at: datetime = Field(..., example="2024-01-15T10:30:00Z")
completed_at: Optional[datetime] = Field(None, example="2024-01-15T10:35:00Z")
class Config:
schema_extra = {
"example": {
"id": "tr_abc123",
"status": "processing",
"progress_percent": 65,
"current_step": "Translating slide 3/5",
"error_message": None,
"created_at": "2024-01-15T10:30:00Z",
"completed_at": None
}
}
class LanguageResponse(BaseModel):
"""Response model for supported languages"""
languages: List[dict] = Field(
...,
example=[
{"code": "en", "name": "English"},
{"code": "fr", "name": "French"},
{"code": "de", "name": "German"}
],
description="List of supported languages"
)
Fichier schemas/auth.py à créer
"""Pydantic models for authentication endpoints"""
from pydantic import BaseModel, Field, EmailStr
from typing import Optional
from datetime import datetime
class RegisterRequest(BaseModel):
"""Request model for user registration"""
email: EmailStr = Field(
...,
example="user@example.com",
description="User email address"
)
password: str = Field(
...,
example="SecureP@ss123",
description="User password (min 8 characters)",
min_length=8
)
class LoginRequest(BaseModel):
"""Request model for user login"""
email: EmailStr = Field(
...,
example="user@example.com",
description="User email address"
)
password: str = Field(
...,
example="SecureP@ss123",
description="User password"
)
class TokenResponse(BaseModel):
"""Response model for authentication tokens"""
access_token: str = Field(..., example="eyJhbGciOiJIUzI1NiIs...", description="JWT access token (15min expiry)")
refresh_token: str = Field(..., example="eyJhbGciOiJIUzI1NiIs...", description="JWT refresh token (7 days expiry)")
token_type: str = Field(default="bearer", example="bearer", description="Token type")
expires_in: int = Field(default=900, example=900, description="Access token expiry in seconds")
class Config:
schema_extra = {
"example": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 900
}
}
class LogoutResponse(BaseModel):
"""Response model for logout"""
message: str = Field(default="Successfully logged out", example="Successfully logged out")
class RefreshRequest(BaseModel):
"""Request model for token refresh"""
refresh_token: str = Field(..., example="eyJhbGciOiJIUzI1NiIs...", description="Refresh token")
Fichier schemas/errors.py à créer
"""Pydantic models for error responses"""
from enum import Enum
from pydantic import BaseModel, Field
from typing import Optional, Dict, Any
class ErrorCode(str, Enum):
"""All error codes used in the API"""
# Client errors (4xx)
INVALID_FORMAT = "INVALID_FORMAT"
CORRUPTED_FILE = "CORRUPTED_FILE"
FILE_TOO_LARGE = "FILE_TOO_LARGE"
URL_DOWNLOAD_FAILED = "URL_DOWNLOAD_FAILED"
URL_UNREACHABLE = "URL_UNREACHABLE"
QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
UNAUTHORIZED = "UNAUTHORIZED"
FORBIDDEN = "FORBIDDEN"
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
USER_NOT_FOUND = "USER_NOT_FOUND"
EMAIL_EXISTS = "EMAIL_EXISTS"
INVALID_EMAIL = "INVALID_EMAIL"
TOKEN_EXPIRED = "TOKEN_EXPIRED"
MISSING_API_KEY = "MISSING_API_KEY"
INVALID_API_KEY = "INVALID_API_KEY"
API_KEY_REVOKED = "API_KEY_REVOKED"
PRO_FEATURE_REQUIRED = "PRO_FEATURE_REQUIRED"
GLOSSARY_NOT_FOUND = "GLOSSARY_NOT_FOUND"
PROMPT_NOT_FOUND = "PROMPT_NOT_FOUND"
INVALID_WEBHOOK_URL = "INVALID_WEBHOOK_URL"
FILE_EXPIRED = "FILE_EXPIRED"
# Provider errors (5xx but not 500)
PROVIDER_UNAVAILABLE = "PROVIDER_UNAVAILABLE"
PROVIDER_RATE_LIMITED = "PROVIDER_RATE_LIMITED"
ALL_PROVIDERS_FAILED = "ALL_PROVIDERS_FAILED"
WEBHOOK_FAILED = "WEBHOOK_FAILED"
# System errors (5xx)
INTERNAL_ERROR = "INTERNAL_ERROR"
class ErrorResponse(BaseModel):
"""Standard error response format"""
error: ErrorCode = Field(..., description="Error code")
message: str = Field(..., description="Human-readable error message in French")
details: Optional[Dict[str, Any]] = Field(None, description="Additional error context")
class Config:
schema_extra = {
"example": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {"accepted_formats": [".xlsx", ".docx", ".pptx"]}
}
}
Modification de main.py - Configuration OpenAPI
"""
Document Translation API
FastAPI application for translating complex documents while preserving formatting
"""
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from contextlib import asynccontextmanager
from config import config
from routes.api_v1_router import router as api_v1_router
# Lifespan context manager
@asynccontextmanager
async def lifespan(app: FastAPI):
# ... (garder le code existant)
pass
def custom_openapi():
"""Generate custom OpenAPI schema with comprehensive documentation"""
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Office Translator API",
version="1.0.0",
description="""
API de traduction de documents Office avec préservation parfaite du format.
## Authentification
L'API supporte deux méthodes d'authentification:
### 1. JWT (Web Dashboard & Admin)
Utilisé pour l'interface web et le dashboard admin.
**Obtenir un token:**
```bash
POST /api/v1/auth/login
{
"email": "user@example.com",
"password": "password123"
}
Utiliser le token:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Détails:
- Access token expire en 15 minutes
- Refresh token expire en 7 jours
- Utilisez
/api/v1/auth/refreshpour renouveler l'access token
2. API Key (Automation)
Utilisé pour l'automatisation et l'intégration (Pro users only).
Obtenir une clé:
POST /api/v1/api-keys
Authorization: Bearer <jwt_token>
Utiliser la clé:
X-API-Key: sk_live_abc123def456...
Détails:
- Clé statique, pas d'expiration
- Peut être révoquée à tout moment
- Uniquement pour utilisateurs Pro
Endpoints Principaux
Translation
POST /api/v1/translate- Traduire un documentGET /api/v1/translations/{id}- Vérifier le statutGET /api/v1/download/{id}- Télécharger le fichier traduitGET /api/v1/languages- Langues supportées
Authentication
POST /api/v1/auth/register- Créer un comptePOST /api/v1/auth/login- ConnexionPOST /api/v1/auth/logout- DéconnexionPOST /api/v1/auth/refresh- Renouveler le token
API Keys (Pro)
POST /api/v1/api-keys- Générer une cléGET /api/v1/api-keys- Lister les clésDELETE /api/v1/api-keys/{key_id}- Révoquer une clé
Admin
POST /api/v1/admin/login- Connexion adminGET /api/v1/admin/dashboard- Dashboard adminGET /api/v1/admin/users- Gestion utilisateursPATCH /api/v1/admin/users/{user_id}- Modifier tier utilisateur
Format des Réponses
Succès
{
"data": {
"id": "tr_abc123",
"status": "processing",
"file_name": "report.xlsx"
},
"meta": {
"rate_limit_remaining": 45,
"provider_used": "google"
}
}
Erreur
{
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {
"accepted_formats": [".xlsx", ".docx", ".pptx"]
}
}
Codes d'Erreur Courants
| Code | HTTP | Description |
|---|---|---|
INVALID_FORMAT |
400 | Format fichier non supporté |
FILE_TOO_LARGE |
413 | Fichier > 50 MB |
QUOTA_EXCEEDED |
429 | Limite quotidienne atteinte |
UNAUTHORIZED |
401 | Token/API key invalide |
FORBIDDEN |
403 | Pas les droits requis |
PRO_FEATURE_REQUIRED |
403 | Feature réservée Pro |
PROVIDER_ERROR |
502 | Erreur provider externe |
Rate Limiting
- Free: 5 fichiers par jour
- Pro: Illimité (fair use policy)
- Rate limit info dans
meta.rate_limit_remaining - Header
Retry-Aftersi quota dépassé
Formats Supportés
- Excel: .xlsx
- Word: .docx
- PowerPoint: .pptx
- Taille max: 50 MB
Langues Supportées
Utilisez GET /api/v1/languages pour obtenir la liste complète.
Codes ISO 639-1 (ex: en, fr, de, es, it, pt, ja, zh, ar, ru...)
Webhooks (Pro)
Spécifiez webhook_url dans votre requête pour recevoir une notification POST quand la traduction termine.
Payload envoyé:
{
"translation_id": "tr_abc123",
"status": "completed",
"timestamp": "2024-01-15T10:35:00Z",
"file_name": "report.xlsx",
"error_message": null
}
Glossaires & Custom Prompts (Pro)
Personnalisez les traductions LLM:
- Glossaires: Termes spécifiques à votre domaine
- Custom Prompts: Instructions contextuelles pour le LLM
Voir endpoints /api/v1/glossaries et /api/v1/prompts.
""",
routes=app.routes,
)
# Configuration des security schemes
openapi_schema["components"]["securitySchemes"] = {
"JWT": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "JWT token from /api/v1/auth/login. Format: Bearer <token>"
},
"APIKey": {
"type": "apiKey",
"in": "header",
"name": "X-API-Key",
"description": "API Key from /api/v1/api-keys (Pro users only). Format: sk_live_..."
}
}
# Tags pour grouper les endpoints
openapi_schema["tags"] = [
{"name": "Translation", "description": "Document translation endpoints"},
{"name": "Authentication", "description": "User authentication (JWT)"},
{"name": "API Keys", "description": "API key management (Pro users)"},
{"name": "Admin", "description": "Admin dashboard endpoints"},
{"name": "Health", "description": "Health check endpoints"},
{"name": "Legacy", "description": "Legacy/utility endpoints"},
]
app.openapi_schema = openapi_schema
return app.openapi_schema
Create FastAPI app
app = FastAPI( title="Office Translator API", version="1.0.0", description="API de traduction de documents Office", docs_url="/docs", # Swagger UI redoc_url="/redoc", # ReDoc openapi_url="/openapi.json", # OpenAPI spec contact={ "name": "Office Translator Support", "email": "support@office-translator.com", }, license_info={ "name": "Proprietary", }, lifespan=lifespan, )
Apply custom OpenAPI schema
app.openapi = custom_openapi
Add middleware (garder le code existant)
...
============== Exception Handlers (garder le code existant) ==============
...
============== Health Check Endpoints (SANS préfixe - K8s probes) ==============
@app.get("/health", tags=["Health"]) async def health_check(): """Health check endpoint with detailed system status""" # ... (garder le code existant) pass
@app.get("/ready", tags=["Health"]) async def readiness_check(): """Kubernetes readiness probe""" # ... (garder le code existant) pass
============== Root Endpoint ==============
@app.get("/", tags=["Health"]) async def root(): """Root endpoint with API information""" return { "name": "Office Translator API", "version": "1.0.0", "status": "operational", "docs": "/docs", "redoc": "/redoc", "api_base": "/api/v1", }
============== API v1 Routes ==============
app.include_router(api_v1_router)
### Exemple de Documentation d'Endpoint (routes/translate_routes.py)
```python
from fastapi import APIRouter, UploadFile, File, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import Optional
from schemas.translation import TranslateRequest, TranslateResponse, TranslationStatusResponse
from schemas.errors import ErrorResponse, ErrorCode
router_v1 = APIRouter(prefix="/api/v1", tags=["Translation"])
security = HTTPBearer(auto_error=False)
@router_v1.post(
"/translate",
response_model=TranslateResponse,
status_code=status.HTTP_202_ACCEPTED,
summary="Translate a document",
description="""
Upload a document for translation while preserving formatting.
**Supported formats:** .xlsx, .docx, .pptx
**Max file size:** 50 MB
**Authentication:** JWT Bearer token or X-API-Key header
**Translation modes:**
- `classic`: Google Translate or DeepL (all users)
- `llm`: Ollama or OpenAI (Pro users only)
**Webhooks:**
Specify `webhook_url` to receive a POST notification when translation completes.
""",
responses={
202: {
"description": "Translation job created and processing started",
"content": {
"application/json": {
"example": {
"id": "tr_abc123",
"status": "processing",
"file_name": "report.xlsx",
"source_lang": "en",
"target_lang": "fr",
"created_at": "2024-01-15T10:30:00Z"
}
}
}
},
400: {
"model": ErrorResponse,
"description": "Invalid request (unsupported format, corrupted file)",
"content": {
"application/json": {
"examples": {
"INVALID_FORMAT": {
"summary": "Unsupported file format",
"value": {
"error": "INVALID_FORMAT",
"message": "Format PDF non supporté. Formats acceptés: .xlsx, .docx, .pptx",
"details": {"accepted_formats": [".xlsx", ".docx", ".pptx"]}
}
},
"CORRUPTED_FILE": {
"summary": "File is corrupted",
"value": {
"error": "CORRUPTED_FILE",
"message": "Le fichier est corrompu ou illisible",
"details": None
}
},
"FILE_TOO_LARGE": {
"summary": "File exceeds size limit",
"value": {
"error": "FILE_TOO_LARGE",
"message": "Le fichier dépasse la limite de 50 MB",
"details": {"max_size_mb": 50, "actual_size_mb": 65}
}
}
}
}
}
},
401: {
"model": ErrorResponse,
"description": "Authentication required or invalid",
"content": {
"application/json": {
"examples": {
"UNAUTHORIZED": {
"summary": "Missing or invalid authentication",
"value": {
"error": "UNAUTHORIZED",
"message": "Authentification requise",
"details": None
}
},
"INVALID_API_KEY": {
"summary": "Invalid API key",
"value": {
"error": "INVALID_API_KEY",
"message": "Clé API invalide",
"details": None
}
}
}
}
}
},
403: {
"model": ErrorResponse,
"description": "Feature not available for user tier",
"content": {
"application/json": {
"example": {
"error": "PRO_FEATURE_REQUIRED",
"message": "La traduction LLM est réservée aux utilisateurs Pro",
"details": {"current_tier": "free", "required_tier": "pro"}
}
}
}
},
429: {
"model": ErrorResponse,
"description": "Daily quota exceeded",
"content": {
"application/json": {
"example": {
"error": "QUOTA_EXCEEDED",
"message": "Limite quotidienne de 5 traductions atteinte",
"details": {
"current_usage": 5,
"limit": 5,
"reset_at": "2024-01-16T00:00:00Z"
}
}
}
}
},
}
)
async def translate_document(
file: UploadFile = File(..., description="Document to translate (.xlsx, .docx, .pptx, max 50MB)"),
source_lang: str = Form(..., description="Source language code (ISO 639-1)"),
target_lang: str = Form(..., description="Target language code (ISO 639-1)"),
mode: str = Form("classic", description="Translation mode: 'classic' or 'llm'"),
provider: Optional[str] = Form(None, description="Specific provider (optional)"),
webhook_url: Optional[str] = Form(None, description="Webhook URL for completion notification (optional)"),
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
):
"""
Translate a document while preserving formatting.
- **file**: Document to translate (xlsx, docx, pptx only, max 50MB)
- **source_lang**: Source language code (ISO 639-1, e.g., 'en')
- **target_lang**: Target language code (ISO 639-1, e.g., 'fr')
- **mode**: Translation mode - 'classic' (Google/DeepL) or 'llm' (Ollama/OpenAI)
- **provider**: Specific provider to use (optional, uses fallback chain if not specified)
- **webhook_url**: URL to receive POST notification when complete (optional)
Returns job ID for tracking progress via GET /api/v1/translations/{id}
"""
pass
Dev Agent Record
Agent Model Used
Claude 3.5 Sonnet (claude-3-5-sonnet)
Debug Log References
- Tests passent: 18/18 tests passent
- Import vérifié:
from main import appfonctionne correctement - OpenAPI 3.1.0 généré par FastAPI/Pydantic v2
Completion Notes List
- ✅ Analyse exhaustive du contexte terminée
- ✅ Documentation FastAPI OpenAPI configurée avec
custom_openapi() - ✅ Security schemes JWT et API Key documentés
- ✅ Tags configurés pour grouper les endpoints (Translation, Authentication, API Keys, Admin, Health, Legacy)
- ✅ Schémas Pydantic créés dans
schemas/pour tous les domaines - ✅ Exemples de request/response ajoutés aux modèles Pydantic
- ✅ Codes d'erreur documentés dans
schemas/errors.pyavec ERROR_EXAMPLES - ✅ Endpoints d'authentification documentés avec descriptions et responses
- ✅ Tests complets créés et passent (18/18)
- ✅ Contact et licence configurés dans OpenAPI
File List
schemas/__init__.py- CRÉÉ - Package init avec exportsschemas/translation.py- CRÉÉ - Modèles translation (TranslateResponse, TranslationStatusResponse, etc.)schemas/auth.py- CRÉÉ - Modèles auth (RegisterRequest, LoginRequest, TokenResponse, etc.)schemas/api_keys.py- CRÉÉ - Modèles API keys (APIKeyCreateRequest, APIKeyResponse, etc.)schemas/admin.py- CRÉÉ - Modèles admin (AdminLoginRequest, AdminDashboardResponse, etc.)schemas/errors.py- CRÉÉ - Modèles erreurs (ErrorCode enum, ErrorResponse, ERROR_EXAMPLES)schemas/common.py- CRÉÉ - Modèles communs (HealthCheckResponse, ReadyCheckResponse, etc.)main.py- MODIFIÉ - Configuré custom_openapi() avec description complète et security schemesroutes/translate_routes.py- MODIFIÉ - Ajouté docstrings et descriptionsroutes/auth_routes.py- MODIFIÉ - Ajouté documentation OpenAPI complète pour /register, /login, /logouttests/test_story_3_6_openapi_documentation.py- CRÉÉ - 18 tests pour valider la documentation
Change Log
- 2026-02-22: Story créée avec contexte complet
- 2026-02-22: Implémentation terminée - tous les tests passent (18/18)
Checklist de Validation
Avant de marquer cette story comme terminée, vérifier:
/docsaffiche Swagger UI avec tous les endpoints/redocaffiche ReDoc correctement- Tous les endpoints ont des descriptions complètes
- Tous les request/response schemas sont documentés
- Les exemples sont visibles dans Swagger UI
- Les méthodes d'authentification sont documentées
- Tous les codes d'erreur sont documentés
- "Try It Out" fonctionne sur différents endpoints
- L'authentification JWT fonctionne dans Swagger UI
- L'authentification API Key fonctionne dans Swagger UI
- Les tags regroupent correctement les endpoints
- La version API est visible dans la documentation
- Tous les tests passent