Files
office_translator/_bmad-output/implementation-artifacts/code-review-1-1-to-1-8.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

171 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Revue de code adverse — Stories 1-1 à 1-8 (Epic 1)
**Workflow:** code-review
**Date:** 2026-02-21
**Revu par:** Agent (adversarial senior developer)
**Stories:** 1-1, 1-2, 1-3, 1-4, 1-5, 1-6, 1-7, 1-8
**Contexte:** Comparaison Git vs File List des stories, validation des AC et des tâches [x], qualité de code, sécurité, perf, conformité architecture.
---
## Discrepancies Git vs Story File List
- **Fichiers modifiés (git)** : `.env.example`, `alembic/env.py`, `database/*`, `main.py`, `models/subscription.py`, `requirements.txt`, `routes/auth_routes.py`, `services/auth_service.py`
- **Fichiers non suivis (??)** : `alembic/versions/002_*`, `database/utils.py`, `middleware/tier_quota.py`, `tests/*`, `pytest.ini`
- **Constat** : Les fichiers créés (migrations, utils, middleware, tests) ne sont pas commités — **MEDIUM** (transparence / traçabilité). Les File List des stories sont globalement alignées avec le code touché.
---
## Story 1-1 : Setup base de données & modèle User
**Statut story :** in-progress
**Sprint :** in-progress
### Problèmes trouvés (5)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| HIGH | 1-1-H1 | **UserRepository.create() ne reçoit pas `subscription_status`**`auth_service.create_user()` appelle `repo.create(..., subscription_status=SubscriptionStatus.ACTIVE.value)` alors que la signature de `UserRepository.create()` na que `email, name, password_hash, plan`. → **TypeError** à linscription avec DB. | `services/auth_service.py:285-291` vs `database/repositories.py:41-56` |
| MEDIUM | 1-1-M1 | **`datetime.utcnow()` déprécié** — Larchitecture impose `datetime.now(timezone.utc)`. Utilisé dans `auth_service` (create_access_token, create_refresh_token, _db_user_to_model, check_usage_limits, update_user JSON path) et dans `repositories` (increment_usage, update_status). | `services/auth_service.py` (multiples), `database/repositories.py:99, 241` |
| MEDIUM | 1-1-M2 | **UserRepository.create(plan=PlanType.FREE.value)** — On passe une `str` alors que la signature attend `PlanType`. Fonctionne avec lEnum SQLAlchemy mais incohérent pour le typage et la maintenance. | `services/auth_service.py:289` |
| LOW | 1-1-L1 | **Task 1.5 (UUID PK) reportée** — La story le documente comme DEFERRED ; pas de preuve dans le code que la migration coordonnée est planifiée (lien vers une story/ticket). | Story 1-1, Task 1.5 |
| LOW | 1-1-L2 | **Modèle User**`to_dict()` nexpose pas `tier` ni `daily_translation_count` alors quils sont centraux pour lAPI (rate limit, billing). Risque dincohérence si dautres chemins sappuient sur `to_dict()`. | `database/models.py:121-138` |
---
## Story 1-2 : Inscription utilisateur
**Statut story :** done
**Sprint :** done
### Problèmes trouvés (4)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| CRITICAL | 1-2-C1 | **Même bug que 1-1-H1** — Inscription avec DB échoue à cause de `subscription_status` passé à `repo.create()`. Inscription en mode JSON fonctionne ; avec PostgreSQL/SQLite → crash. | `services/auth_service.py:285-291` |
| MEDIUM | 1-2-M1 | **register_v1 retourne `tier`: user.plan.value** — La story et larchitecture parlent de `tier` (free/pro). Si `user` vient de la DB, `_db_user_to_model` mappe `plan` ; le champ `tier` du modèle SQLAlchemy nest pas exposé directement sur le modèle Pydantic `User` (subscription). Risque de divergence si plan et tier ne sont pas toujours synchronisés. | `routes/auth_routes.py:382-388` |
| LOW | 1-2-L1 | **Réponse 201** — AC respectée (201 + user data). Pas de `meta.rate_limit_remaining` sur register ; acceptable car pas encore de traduction. | — |
| LOW | 1-2-L2 | **File List story 1-2** — Liste des fichiers modifiés (dont requirements, database, alembic) reflète des changements “hors story” ; utile pour traçabilité mais à garder cohérent avec les stories 1-1 / 1-2. | Story 1-2, File List |
---
## Story 1-3 : Login utilisateur (JWT)
**Statut story :** done
### Problèmes trouvés (4)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-3-M1 | **login_v1 utilise `user.password_hash`** — Propriété dépréciée (alias de `hashed_password`). Préférable dutiliser `user.hashed_password` si le modèle subscription lexpose, sinon documenter la dépendance au legacy. | `routes/auth_routes.py:419` (modèle subscription User) |
| MEDIUM | 1-3-M2 | **Tokens JWT**`create_access_token` / `create_refresh_token` utilisent `datetime.utcnow()` au lieu de `datetime.now(timezone.utc)` (dépréciation Python 3.12+ et bonnes pratiques timezone). | `services/auth_service.py:141-144, 165-168` |
| LOW | 1-3-L1 | **AC2 (JWT signing)** — SECRET_KEY depuis lenv (JWT_SECRET / JWT_SECRET_KEY) ; OK. Pas de fallback en production à documenter. | — |
| LOW | 1-3-L2 | **AC3/AC4 (INVALID_CREDENTIALS)** — Même message pour “user not found” et “wrong password” ; conforme à la story (anti-énumération). | — |
---
## Story 1-4 : Logout utilisateur
**Statut story :** done
### Problèmes trouvés (3)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-4-M1 | **Blocklist JTI en mémoire** — Documenté comme non thread-safe et perdu au redémarrage. Pour un déploiement multi-worker, la révocation ne sera pas partagée. Story 1.6 introduit Redis ; pas de migration de la blocklist vers Redis dans la codebase. | `services/auth_service.py:76-94`, Story 1-4 Dev Notes |
| LOW | 1-4-L1 | **Corps logout optionnel**`request.json()` en cas dexception → pass ; correct. Pas de validation stricte du body si présent. | `routes/auth_routes.py:415-423` |
| LOW | 1-4-L2 | **Backward compat tokens sans JTI** — Gérée (payload.get("jti") → pas de révocation). OK. | — |
---
## Story 1-5 : Refresh token
**Statut story :** done
### Problèmes trouvés (3)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-5-M1 | **refresh_v1** — Utilise `user.plan.value` pour `create_access_token(..., tier=user.plan.value)`. Cohérent avec login_v1 ; même remarque que 1-2-M1 si un jour `tier` et `plan` divergent. | `routes/auth_routes.py:431-432` |
| LOW | 1-5-L1 | **Guard `isinstance(body, dict)`** — Présent ; évite 500 sur body non-objet. OK. | `routes/auth_routes.py:401-409` |
| LOW | 1-5-L2 | **Rotation refresh token** — Nouveau refresh_token émis à chaque refresh ; ancien non révoqué explicitement (pas demandé par la story). À considérer pour une future “rotation” sécurisée. | — |
---
## Story 1-6 : Middleware rate limiting par tier
**Statut story :** done
### Problèmes trouvés (5)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-6-M1 | **AC5 (meta.rate_limit_remaining)** — Larchitecture prévoit `meta.rate_limit_remaining` dans le **body** JSON. Limplémentation renvoie **FileResponse** pour `/translate` et met linfo dans les **headers** (`X-Rate-Limit-Remaining`, `X-Rate-Limit-Reset-At`). Pour un endpoint qui retourne du JSON, il faudrait `meta` dans le body ; pour un fichier, les headers sont une alternative raisonnable mais à documenter par rapport à lAC. | `main.py:621-678` ; Story 1-6 Completion Notes |
| MEDIUM | 1-6-M2 | **Tier pour quota**`_tier_for_quota(current_user.plan)` utilise `plan` (PlanType). tier_quota accepte une string ("free"/"pro" etc.) ; cohérent avec `tier` en DB. Vérifier que tous les chemins (API key, admin) passent bien un tier aligné. | `main.py:496, 612` ; `middleware/tier_quota.py:129` |
| LOW | 1-6-L1 | **Sliding window** — AC4 demande “sliding window”. Implémentation par clé Redis `rate_limit:daily:{user_id}:{YYYY-MM-DD}` = fenêtre par jour UTC, pas fenêtre glissante 24h. Cest un “fixed window” par jour. À aligner avec le libellé AC ou documenter le choix. | `middleware/tier_quota.py`, AC4 |
| LOW | 1-6-L2 | **Fallback in-memory** — Par process ; documenté. OK pour MVP. | — |
| LOW | 1-6-L3 | **auth_update_user en to_thread** — Utilisation de `asyncio.to_thread` pour ne pas bloquer levent loop ; OK. | `main.py:632-636` |
---
## Story 1-7 : Admin changement de tier manuel
**Statut story :** done
### Problèmes trouvés (4)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-7-M1 | **Préfixe endpoint** — Lepic indique `PATCH /api/v1/admin/users/{user_id}`. Si lapp expose `PATCH /admin/users/{user_id}` (sans `/api/v1`), cest une déviation par rapport à la convention du reste de lAPI v1. À documenter ou aligner. | Story 1-7, main.py (route admin) |
| LOW | 1-7-L1 | **Réponses 404/400** — Format `{ error, message, details? }` sans `data` ; conforme à la revue précédente. | — |
| LOW | 1-7-L2 | **Audit log** — event admin_tier_change avec target_user_id, new_tier, timestamp ; pas de contenu document. OK. | — |
| LOW | 1-7-L3 | **Literal plan** — Schéma Pydantic avec Literal pour les plans ; 422 sur valeur invalide. OK. | — |
---
## Story 1-8 : Tracking usage pour billing
**Statut story :** review
### Problèmes trouvés (5)
| Sévérité | Id | Description | Fichier:ligne / preuve |
|----------|------|-------------|-------------------------|
| MEDIUM | 1-8-M1 | **create_completed dans to_thread** — La création du log de traduction est dans un `asyncio.to_thread(_create_translation_log)`. Si la session sync est utilisée ailleurs dans le même flux, attention aux risques de concurrence / fermeture de session. Le context manager `get_sync_session()` est bien utilisé dans la fonction passée à to_thread. | `main.py:641-658` |
| MEDIUM | 1-8-M2 | **update_status(..., status="completed")** — Utilise `datetime.utcnow()` pour `completed_at` au lieu de `datetime.now(timezone.utc)`. Incohérent avec `create_completed` qui utilise timezone.utc. | `database/repositories.py:241` |
| LOW | 1-8-L1 | **Champs translation_log** — AC2 demande user_id, file_name, file_size, timestamp, status, provider_used. Le modèle `Translation` a original_filename, file_size_bytes, created_at, completed_at, status, provider. completed_at est renseigné dans create_completed ; OK. | `database/models.py:141-184` |
| LOW | 1-8-L2 | **Pas de contenu fichier** — Aucun champ ne stocke le contenu ; NFR11/NFR16 respectés. | — |
| LOW | 1-8-L3 | **Skip si pas DB** — Si USE_DATABASE ou DATABASE_AVAILABLE est False, pas dentrée Translation ; documenté. OK. | `main.py:643-644` |
---
## Synthèse
| Sévérité | Nombre |
|-----------|--------|
| CRITICAL | 1 (1-2-C1 = 1-1-H1, inscription DB) |
| HIGH | 1 (1-1-H1) |
| MEDIUM | 12+ |
| LOW | 15+ |
**Problème bloquant commun aux stories 1-1 et 1-2 :**
`UserRepository.create()` ne prend pas `subscription_status` ; lappel dans `auth_service.create_user()` avec DB lève **TypeError** et casse linscription.
**Recommandations prioritaires :**
1. **Corriger lappel à `UserRepository.create()`** : soit retirer largument `subscription_status` de lappel dans `auth_service.create_user()` (le repo met déjà `SubscriptionStatus.ACTIVE` dans le modèle), soit ajouter le paramètre au repository et le faire passer jusquau modèle. Recommandation : retirer largument dans `auth_service`.
2. **Remplacer `datetime.utcnow()`** par `datetime.now(timezone.utc)` dans `auth_service` et `repositories` (migrations, completed_at, usage_reset, etc.).
3. **Clarifier AC5 story 1-6** : documenter que pour `POST /translate` (FileResponse), le rate limit est exposé en headers et non dans un body `meta`.
---
## Suite à donner
Conformément au workflow (étape 4) :
1. **Corriger automatiquement** — Appliquer les correctifs (au moins CRITICAL + HIGH + MEDIUM listés ci-dessus) dans le code et les tests.
2. **Créer des action items** — Ajouter une sous-section « Review Follow-ups (AI) » dans les tâches des stories concernées avec des items `- [ ] [AI-Review][Severity] Description [file:line]`.
3. **Détails** — Approfondir un point précis (indiquer le numéro dissue ou la story).
Réponse attendue : **[1], [2] ou [3]** (ou combinaison, ex. « 1 pour C1/H1, 2 pour le reste »).