""" Schémas Pydantic — authentification et gestion des clients API """ from datetime import datetime from typing import List, Optional, Set from pydantic import BaseModel, ConfigDict, Field, field_validator from app.models.client import ClientPlan # Scopes valides — toute valeur hors de cet ensemble est rejetée VALID_SCOPES: Set[str] = { "images:read", "images:write", "images:delete", "ai:use", "admin", } # ───────────────────────────────────────────────────────────── # Requêtes # ───────────────────────────────────────────────────────────── class ClientCreate(BaseModel): """Créer un nouveau client API.""" name: str = Field(..., min_length=1, max_length=256, description="Nom de l'application cliente") scopes: List[str] = Field( default=["images:read", "images:write"], description="Permissions accordées", ) plan: ClientPlan = Field(default=ClientPlan.FREE, description="Plan tarifaire") @field_validator("scopes") @classmethod def validate_scopes(cls, v): invalid = set(v) - VALID_SCOPES if invalid: raise ValueError( f"Scopes invalides : {invalid}. " f"Scopes valides : {sorted(VALID_SCOPES)}" ) return v class ClientUpdate(BaseModel): """Modifier un client API existant.""" name: Optional[str] = Field(None, min_length=1, max_length=256) scopes: Optional[List[str]] = None plan: Optional[ClientPlan] = None is_active: Optional[bool] = None # ───────────────────────────────────────────────────────────── # Réponses # ───────────────────────────────────────────────────────────── class ClientResponse(BaseModel): """Réponse de base pour un client API.""" model_config = ConfigDict(from_attributes=True) id: str name: str scopes: List[str] plan: ClientPlan is_active: bool created_at: Optional[datetime] = None updated_at: Optional[datetime] = None class ClientCreateResponse(ClientResponse): """ Réponse après création d'un client. La clé API est retournée EN CLAIR une seule fois. """ api_key: str = Field( ..., description="Clé API en clair — stockez-la, elle ne sera plus jamais affichée", ) class KeyRotateResponse(BaseModel): """Réponse après rotation de la clé API.""" id: str api_key: str = Field( ..., description="Nouvelle clé API en clair — stockez-la, elle ne sera plus jamais affichée", ) message: str = "Clé API régénérée avec succès — l'ancienne clé est désormais invalide"