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>
13 KiB
13 KiB
Story 1.8: Tracking Usage pour Billing
Status: done
Story
As a system, I want to track translation usage per user, so that I can bill Pro users and monitor usage.
Acceptance Criteria
- AC1 : Incrément du compteur quotidien — Given a user completes a translation, when the translation succeeds, then the user's
daily_translation_countis incremented (déjà assuré par Story 1.6 / tier_quota + sync DB dans/translate). - AC2 : Entrée translation_logs — When the translation succeeds, a
translation_logsentry is created (tabletranslations) with:user_id,file_name(original_filename),file_size(file_size_bytes),timestamp(created_at),status("completed"),provider_used(provider). Aucun contenu de fichier n'est jamais enregistré (NFR11, NFR16). - AC3 : Pas de contenu dans les logs — No file content is logged; only metadata (file name, size, timestamp, status, provider). NFR11 et NFR16 respectés.
Tasks / Subtasks
- Task 1 : Créer l'entrée Translation après succès (AC: 2, 3)
- 1.1 Après une traduction réussie dans
POST /translate, si l'utilisateur est authentifié (current_user), créer un enregistrement dans la tabletranslations(modèleTranslation) avec : user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status="completed", completed_at=now. Ne jamais stocker ni logger de contenu de fichier. - 1.2 Utiliser le repository existant (TranslationRepository.create puis update_status "completed" ou créer en "completed" directement si l'API le permet). Vérifier la signature de
createdans database/repositories.py. - 1.3 S'assurer que le provider utilisé (google, deepl, ollama, openai, openrouter, libre) est bien enregistré (variable
providerdans l'endpoint).
- 1.1 Après une traduction réussie dans
- Task 2 : Synchronisation daily_translation_count (AC: 1)
- 2.1 Vérifier que l'incrément Redis (tier_quota_service.increment_on_success) et la mise à jour DB (auth_update_user avec daily_translation_count) restent en place après succès (déjà présents dans main.py).
- 2.2 Si le backend utilise uniquement le stockage JSON (sans DB SQL), documenter le comportement : soit pas d'entrée Translation (optionnel), soit un fichier JSON de "translation_logs" avec les mêmes champs métadonnées uniquement.
- Task 3 : Tests (AC: 1–3)
- 3.1 Test : après une traduction réussie par un utilisateur authentifié, une entrée existe dans
translationsavec user_id, original_filename, file_size_bytes, status="completed", provider correct. - 3.2 Test : aucun champ ne contient de contenu de document (vérifier schéma et politiques de log).
- 3.3 Test : daily_translation_count utilisateur est incrémenté (déjà couvert par tests 1.6/1.7 si besoin de régression).
- 3.1 Test : après une traduction réussie par un utilisateur authentifié, une entrée existe dans
Dev Notes
- Contexte : La table
translations(modèleTranslation) existe déjà dans database/models.py avec user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status, created_at, completed_at. TranslationRepository a une méthodecreate()etupdate_status(). Actuellement, après succès de/translate, on incrémente le quota (Redis + daily_translation_count en DB) mais on ne crée pas d'entrée danstranslations. La story consiste à créer cette entrée à chaque traduction réussie (utilisateur authentifié). - Provider : Dans main.py l'endpoint reçoit
provider(openrouter, google, ollama, deepl, libre, openai). Utiliser cette valeur pourprovider_used. - Fichier : file.filename (original), file_size peut être obtenu après save ou via validation_result / output_info. Utiliser file_size_bytes du fichier source (input_path) ou de la réponse si disponible.
Project Structure Notes
- Backend : main.py (POST /translate) ; database/models.py (Translation) ; database/repositories.py (TranslationRepository.create, update_status). Pas de nouveau module requis ; brancher l’appel au repository après la traduction réussie (après tier_quota increment et avant/suite auth_update_user).
References
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.8]
- [Source: database/models.py — Translation model]
- [Source: database/repositories.py — TranslationRepository]
- [Source: main.py — POST /translate, tier_quota increment]
- [Source: NFR11, NFR16 — no document content in logs]
Developer Context (Guardrails)
Technical requirements
- Backend : FastAPI. Après une traduction réussie dans
POST /translate, sicurrent_userest défini : (1) garder l’appel àtier_quota_service.increment_on_success(current_user.id)etauth_update_user(..., daily_translation_count + 1); (2) créer un enregistrementTranslationvia le repository (DB) avec user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status="completed", completed_at=now. Ne jamais persister ni logger de contenu de fichier. - DB : Utiliser la table existante
translations. TranslationRepository.create() attend source_language, target_language, provider, file_type, file_size_bytes, page_count (optionnel). Puis appeler update_status(id, "completed") ou adapter create pour accepter status="completed" et completed_at si besoin. - Session/DB : S’assurer d’utiliser la même session/engine que le reste de l’app (get_db ou équivalent dans main.py). Si l’app utilise uniquement auth en JSON sans DB, ne pas créer d’entrée Translation (ou prévoir un stockage fichier JSON pour “translation_logs” métadonnées uniquement).
Architecture compliance
- Conventions API : pas de changement d’API publique ; comportement interne uniquement (enregistrement pour billing/analytics).
- Format des données : snake_case en DB et en JSON. Pas d’exposition du contenu des documents (NFR11, NFR16).
Library / framework
- SQLAlchemy / repository existant (TranslationRepository). Pas de nouvelle lib.
File structure
- main.py : après succès de la traduction (après excel_translator.translate_file / word / pptx), appeler le repository pour créer l’entrée Translation (avec get_db ou session injectée). Si plusieurs chemins (sync/async), utiliser la même session que pour auth_update_user (asyncio.to_thread si blocage).
- database/repositories.py : utiliser create() existant ; ajouter si besoin une méthode create_completed() qui crée directement en status "completed" avec completed_at pour éviter deux appels (create + update_status).
Testing requirements
- Tests d’intégration ou unitaires : (1) traduction réussie avec utilisateur authentifié → une ligne dans
translationsavec les bons champs (user_id, original_filename, file_size_bytes, status="completed", provider) ; (2) aucun champ ne contient de texte traduit ou contenu binaire ; (3) régression : daily_translation_count et quota Redis inchangés (toujours incrémentés). Utiliser un mock du translate_file pour ne pas dépendre des vrais providers.
Previous Story Intelligence (1-7 Admin - Changement de Tier Manuel)
- Fichiers modifiés : main.py (PATCH /admin/users/{user_id}), services/auth_service.py (update_user_plan), database/repositories.py (updated_at UTC), tests/test_admin_tier_change.py.
- Patterns : Réponses erreur
{ error, message, details? }sans champdata. Utiliser Depends(require_admin) pour les routes admin. Pour la story 1.8, pas de route admin ; uniquement logique côté POST /translate et DB. - DB : User avec tier/plan ; pas de changement de schéma User pour 1.8. Table Translation déjà présente.
Git Intelligence Summary
- Codebase : POST /translate dans main.py fait déjà increment_on_success + auth_update_user(daily_translation_count+1). Il manque la création de l’entrée Translation (table
translations) pour le billing/analytics. TranslationRepository.create() et update_status() existent ; il reste à les appeler au bon endroit avec les paramètres disponibles (file.filename, provider, target_language, source_language, file size depuis input_path ou file).
Latest Tech Information
- FastAPI : Pour accéder à la DB dans un endpoint async, utiliser soit
Depends(get_db)(session synchrone) puis asyncio.to_thread pour les appels repository, soit une session async si le projet est passé en SQLAlchemy async. Vérifier database/connection.py et comment get_db est utilisé dans main.py. - SQLAlchemy : Translation model a déjà les champs nécessaires ; completed_at doit être renseigné manuellement si on crée en "completed" (datetime.now(timezone.utc)).
Project Context Reference
- Structure : main.py contient POST /translate ; après la traduction (excel_translator.translate_file, etc.), le bloc actuel fait tier_quota increment et auth_update_user. Insérer la création de l’enregistrement Translation dans ce même bloc (après succès, avec user_id, filename, size, provider, status completed). get_db : vérifier si disponible dans main (FastAPI Depends) pour obtenir une session et appeler TranslationRepository(session).create(...).
- Auth : current_user peut être None (traduction sans auth) ; dans ce cas ne pas créer d’entrée Translation (ou créer avec user_id nullable si le schéma le permet — actuellement Translation.user_id est non nullable, donc ne créer que si current_user présent).
- Périmètre : Le translation log (AC2) s’applique à POST /translate uniquement. POST /translate-batch est hors scope pour cette story (pas d’auth utilisateur ni de log par fichier dans l’implémentation actuelle).
Story Completion Status
- Status : done
- Note : Code review (adversarial) 2026-02-21 : correctifs HIGH/MEDIUM/LOW appliqués ; AC2 limité à POST /translate (batch hors scope).
Change Log
- 2026-02-20: Story 1.8 created from epics + architecture + previous story 1-7.
- 2026-02-20: Story 1.8 implémentée : création entrée Translation après succès, get_sync_session, create_completed, tests unitaires et intégration.
- 2026-02-21: Code review (option 1 – correctifs auto) : timezone UTC dans repositories, logger.exception pour échec log, tests unauthenticated + échec log, file_type String(20), double commit supprimé, périmètre batch documenté. Corrections compatibilité auth_service/repository (lazy import get_sync_session, create/update, _db_user_to_model) pour tests intégration.
Dev Agent Record
Agent Model Used
{{agent_model_name_version}}
Debug Log References
Completion Notes List
- Task 1 : Après succès de POST /translate, si current_user et DB (USE_DATABASE + DATABASE_AVAILABLE), création d’un enregistrement Translation via TranslationRepository.create_completed (user_id, original_filename, file_type, file_size_bytes, source_language, target_language, provider, status=completed, completed_at). Taille fichier source via input_path.stat().st_size. Aucun contenu de fichier stocké (AC2, AC3, NFR11, NFR16).
- Task 2 : Vérifié : tier_quota_service.increment_on_success et auth_update_user(daily_translation_count+1) restent en place dans main.py. Sans DB (JSON only), pas d’entrée Translation.
- Task 3 : Tests ajoutés dans tests/test_translation_log_1_8.py : test_create_completed_inserts_row_with_metadata_only, test_translation_model_has_no_content_columns, test_translate_creates_translation_log_when_authenticated_and_db (intégration, skip si requests absent).
- database/connection.py : ajout sync_engine et get_sync_session (context manager) pour utilisation par auth_service et translation log.
- database/repositories.py : ajout TranslationRepository.create_completed() pour créer directement en status "completed" avec completed_at.
File List
- database/connection.py (sync_engine, get_sync_session)
- database/repositories.py (create_completed, completed_at timezone UTC dans update_status)
- database/models.py (Translation.file_type String(20))
- main.py (création translation log après succès si current_user + DB ; logger.exception si échec)
- tests/test_translation_log_1_8.py (nouveau ; tests unauthenticated + échec log ; fixture client_with_db moteur dédié)
- services/auth_service.py (lazy import get_sync_session/UserRepository ; create/update compat repo ; _db_user_to_model getattr champs optionnels — pour tests intégration 1.8)
- _bmad-output/implementation-artifacts/sprint-status.yaml (1-8 → done)
- _bmad-output/implementation-artifacts/1-8-tracking-usage-pour-billing.md (statut, tâches, change log, file list)
Senior Developer Review (AI)
- Date : 2026-02-21
- Résultat : Approuvé après correctifs automatiques (option 1).
- Problèmes traités : H2 timezone UTC dans update_status ; H3 test sans auth → pas de log ; H4 statut story aligné ; H1 batch documenté hors scope ; M1 logger.exception ; M2/M3 tests ; L1 file_type String(20), L2 double commit, L3 importorskip supprimé. Corrections additionnelles pour faire passer les tests intégration : auth_service lazy import get_sync_session, repo.create/update et _db_user_to_model champs optionnels, fixture client_with_db avec moteur dédié.