Files
office_translator/_bmad-output/implementation-artifacts/3-6-documentation-openapi-swagger-redoc.md
Sepehr Ramezani 26bd096a06 feat: production deployment - full update with providers, admin, glossaries, pricing, tests
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>
2026-04-25 15:01:47 +02:00

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

  1. Swagger UI Accessible: GET /docs affiche Swagger UI avec tous les endpoints. (FR35, NFR18)
  2. ReDoc Accessible: GET /redoc affiche ReDoc avec documentation claire et lisible.
  3. Schémas Complets: Tous les request/response schemas sont documentés avec types et descriptions.
  4. Authentification Documentée: Méthodes JWT et API Key sont documentées dans OpenAPI avec exemples.
  5. Codes Erreur Documentés: Tous les codes d'erreur sont listés avec exemples JSON.
  6. Version API: La version (v1) est clairement visible dans la documentation.
  7. 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 HTTPBearer security scheme pour JWT
    • 2.2 Configurer APIKeyHeader security 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
  • Task 3: Documenter les Endpoints de Translation (AC: #3, #5)

    • 3.1 Documenter POST /api/v1/translate avec 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/languages pour les langues supportées
    • 3.5 Ajouter des exemples de request/response pour chaque endpoint
    • 3.6 Documenter tous les codes d'erreur possibles
  • 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
  • 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)
  • 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
  • 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}
  • 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 examples parameter
  • Task 10: Tester la Documentation (AC: Tous)

    • 10.1 Tester que /docs affiche Swagger UI correctement
    • 10.2 Tester que /redoc affiche 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

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

Intelligence des Stories Précédentes (Epic 3)

Story 3.1 (API Key Generation) - Enseignements

  1. Routeur api_key_routes.py utilise prefix="/api/v1/api-keys"
  2. Pattern de réponse {data: {...}, meta: {...}} établi
  3. Tests complets avec fixtures dans tests/conftest.py

Story 3.2 (API Key Revocation User) - Enseignements

  1. Soft delete avec is_active=False
  2. Fonction get_user_by_api_key dans services/auth_service.py
  3. Format d'erreur structuré {error, message, details?}

Story 3.3 (Admin API Key Revocation) - Enseignements

  1. Routes admin dans main.py - à migrer vers routes/admin_routes.py
  2. Dépendance require_admin pour l'authentification admin
  3. Audit logging avec logger.info()

Story 3.4 (API Auth X-API-Key) - Enseignements

  1. Coexistence JWT + API Key dans get_authenticated_user
  2. Middleware d'auth dans routes/translate_routes.py
  3. Priorité API key sur JWT si les deux présents

Story 3.5 (API Versioning) - Enseignements

  1. Tous les endpoints sont maintenant sous /api/v1/
  2. Exceptions documentées: /health, /ready, /docs, /redoc accessibles sans préfixe
  3. Routeur principal routes/api_v1_router.py composé de sous-routeurs
  4. Configuration FastAPI avec title, version, description dans main.py

Intelligence Git (Commits Récents)

Derniers commits pertinents:

  • 3d37ce4: PostgreSQL database infrastructure
  • 550f351: Database migrations avec Alembic
  • c4d6cae: Security hardening, Redis sessions

Patterns identifiés:

  • Tests avec pytest dans tests/
  • Fixtures dans tests/conftest.py
  • Configuration via config.py et 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:

  1. API Keys - Génération (Story 3.1 )
  2. API Keys - Révocation User (Story 3.2 )
  3. API Keys - Révocation Admin (Story 3.3 )
  4. Authentification X-API-Key (Story 3.4 )
  5. API Versioning (Story 3.5 )
  6. Documentation OpenAPI (cette story)
  7. Webhooks (Stories 3.7-3.8 - backlog)
  8. Glossaires (Stories 3.9-3.10 - backlog)
  9. 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

  1. NE PAS supprimer ou déplacer /docs, /redoc, /openapi.json
  2. NE PAS utiliser des docstrings vides ou génériques
  3. NE PAS oublier de documenter les codes d'erreur
  4. NE PAS créer des modèles Pydantic incomplets (tous les champs doivent avoir des descriptions et exemples)
  5. NE PAS ignorer les exemples de request/response
  6. NE PAS oublier de documenter l'authentification (JWT + API Key)

À FAIRE

  1. TOUJOURS ajouter des descriptions complètes dans les endpoints
  2. TOUJOURS créer des modèles Pydantic pour toutes les requests/responses
  3. TOUJOURS documenter TOUS les codes d'erreur possibles
  4. TOUJOURS ajouter des exemples concrets (example parameter dans Field)
  5. TOUJOURS utiliser le dict responses={} pour documenter les erreurs HTTP
  6. CRÉER un fichier schemas/errors.py avec tous les codes d'erreur
  7. CRÉER un fichier schemas/ pour organiser les modèles Pydantic
  8. 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/refresh pour 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 document
  • GET /api/v1/translations/{id} - Vérifier le statut
  • GET /api/v1/download/{id} - Télécharger le fichier traduit
  • GET /api/v1/languages - Langues supportées

Authentication

  • POST /api/v1/auth/register - Créer un compte
  • POST /api/v1/auth/login - Connexion
  • POST /api/v1/auth/logout - Déconnexion
  • POST /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és
  • DELETE /api/v1/api-keys/{key_id} - Révoquer une clé

Admin

  • POST /api/v1/admin/login - Connexion admin
  • GET /api/v1/admin/dashboard - Dashboard admin
  • GET /api/v1/admin/users - Gestion utilisateurs
  • PATCH /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-After si 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 app fonctionne 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.py avec 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 exports
  • schemas/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 schemes
  • routes/translate_routes.py - MODIFIÉ - Ajouté docstrings et descriptions
  • routes/auth_routes.py - MODIFIÉ - Ajouté documentation OpenAPI complète pour /register, /login, /logout
  • tests/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:

  • /docs affiche Swagger UI avec tous les endpoints
  • /redoc affiche 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