Files
office_translator/_bmad-output/implementation-artifacts/1-7-admin-changement-de-tier-manuel.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

164 lines
11 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.
# Story 1.7: Admin - Changement de Tier Manuel
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## Story
As an **Admin**,
I want **to change a user's tier from Free to Pro (or vice versa)**,
so that **I can manually onboard Pro clients**.
## Acceptance Criteria
1. **AC1: Mise à jour immédiate** — Given I am logged in as admin, when I update a user's tier via PATCH /api/v1/admin/users/{user_id} (or PATCH /admin/users/{user_id} si préfixe actuel), then the user's tier/plan is updated immediately in the database.
2. **AC2: Passage à Pro** — If upgraded to Pro (or equivalent plan), user can access LLM modes immediately (rate limit unlimited; tier_quota considère déjà pro/business/enterprise comme illimité).
3. **AC3: Passage à Free** — If downgraded to Free, rate limiting (5 files/day) applies immediately; prochaine requête de traduction respecte le quota free.
4. **AC4: Audit** — Action is logged with timestamp and admin identifier (admin session or user_id if admin is a user); structlog ou équivalent, sans contenu document.
## Tasks / Subtasks
- [x] **Task 1: Endpoint PATCH admin users/{user_id}** (AC: 1, 2, 3)
- [x] 1.1 Add PATCH /api/v1/admin/users/{user_id} (or /admin/users/{user_id} pour cohérence avec GET /admin/users) with body e.g. `{ "tier": "pro" }` or `{ "plan": "pro" }` selon modèle (User.tier string vs User.plan enum).
- [x] 1.2 Protect route with `Depends(require_admin)`.
- [x] 1.3 Resolve user by user_id (UUID); return 404 if user not found.
- [x] 1.4 Validate tier/plan value: allow only "free" | "pro" (et éventuellement starter, business, enterprise si dans PlanType); return 400 for invalid value.
- [x] 1.5 Update User in DB (tier and/or plan); ensure tier_quota and rate limiting use same source (plan or tier).
- [x] **Task 2: Effet immédiat tier** (AC: 2, 3)
- [x] 2.1 No cache invalidation required if tier is read from DB on each request (get_current_user / tier_quota check).
- [x] 2.2 If any in-memory or Redis cache of user tier exists, invalidate or update on PATCH.
- [x] **Task 3: Logging audit** (AC: 4)
- [x] 3.1 Log action: e.g. event "admin_tier_change", user_id target, new_tier, admin_id or "admin_session", timestamp (ISO8601).
- [x] 3.2 Do not log document content; only metadata (NFR11, NFR16).
- [x] **Task 4: Tests** (AC: 14)
- [x] 4.1 Test: admin PATCH with valid tier → 200, user tier updated in DB.
- [x] 4.2 Test: admin PATCH with invalid tier → 400.
- [x] 4.3 Test: admin PATCH with unknown user_id → 404.
- [x] 4.4 Test: non-admin PATCH → 401/403.
- [x] 4.5 Test: after upgrade to pro, user can translate beyond 5/day; after downgrade to free, quota 5 applies.
## Dev Notes
- **Contexte actuel** : GET /admin/users existe dans main.py et utilise `load_users()` (auth_service); les users sont exposés avec plan, email, usage. Il nexiste pas encore de PATCH pour modifier le tier/plan. Le modèle User (database/models.py) a à la fois `tier` (string) et `plan` (PlanType enum: free, starter, pro, business, enterprise). Le middleware tier_quota (story 1.6) utilise le plan pour déterminer si illimité (pro/business/enterprise).
- **Alignement** : Décider si lAPI admin expose `tier` ou `plan`; garder cohérence avec User.tier et User.plan (les deux en sync lors du PATCH).
- **Auth admin** : require_admin (Bearer token après /admin/login) est déjà en place; réutiliser pour le PATCH.
### Project Structure Notes
- Backend à la racine : main.py (routes admin), database/models.py (User), services/auth_service.py (load_users, potentiellement update_user_tier ou équivalent).
- Pas de module admin séparé pour linstant; les routes admin sont dans main.py. Option : ajouter la logique dans main.py ou créer routes/admin_routes.py et linclure.
### References
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.7]
- [Source: _bmad-output/planning-artifacts/architecture.md#API Response Formats]
- [Source: _bmad-output/planning-artifacts/prd.md#FR21, FR22 - Admin change tier]
- [Source: _bmad-output/implementation-artifacts/1-6-middleware-rate-limiting-par-tier.md — tier_quota, plan/tier]
---
## Developer Context (Guardrails)
### Technical requirements
- **Backend** : FastAPI. Endpoint PATCH pour mettre à jour le tier/plan dun utilisateur cible; authentification admin obligatoire (require_admin).
- **DB** : Mise à jour de User.tier et User.plan (ou un seul si un seul est la source de vérité); persistance immédiate.
- **Rate limiting** : Après changement, le prochain appel qui lit le user (get_current_user + tier_quota) doit voir le nouveau tier; pas de cache stale (si cache, invalider).
- **Réponses** : 200 avec `data` contenant lutilisateur mis à jour (id, email, tier/plan); 400 body invalide; 404 user non trouvé; 401/403 non-admin.
### Architecture compliance
- Conventions API : JSON snake_case; format succès `{ data, meta }`; format erreur `{ error, message, details? }` sans champ `data`.
- Préfixe : /api/v1/ pour cohérence avec le reste (epics indiquent PATCH /api/v1/admin/users/{user_id}); si le projet utilise /admin/users sans /api/v1/, documenter et rester cohérent avec GET /admin/users.
### Library / framework
- FastAPI Depends(require_admin) existant.
- SQLAlchemy/session pour update User; utiliser la même couche daccès que auth_service (database/connection, etc.).
### File structure
- Route : main.py (ou routes/admin_routes.py si créé) — PATCH pour users/{user_id}.
- Service : soit dans services/auth_service.py (update_user_plan / update_user_tier), soit dans un nouveau services/admin_service.py.
- Modèle : database/models.py (User.tier, User.plan) — pas de nouvelle table.
### Testing requirements
- Tests dintégration ou unitaires : admin PATCH 200 et vérification en DB; PATCH invalid tier 400; user_id inconnu 404; sans token admin 401/403; test optionnel : après upgrade pro, quota illimité; après downgrade free, 6ème requête 429.
---
## Previous Story Intelligence (1-6 Middleware Rate Limiting par Tier)
- **Fichiers modifiés** : middleware/tier_quota.py (TierQuotaService, Redis + in-memory), main.py (quota check sur /translate, 429 QUOTA_EXCEEDED, X-Rate-Limit-* headers), models/subscription.py (daily_translation_count), database/models.py (User.tier, plan), tests/test_tier_rate_limit.py.
- **Patterns** : Le quota est déterminé par le plan utilisateur (pro/business/enterprise = illimité; free = 5/jour). get_current_user est utilisé pour obtenir lutilisateur; la logique tier est dans tier_quota.check_quota(user_id, plan/tier). Pour la story 1.7, après un PATCH réussi, le prochain appel get_current_user + tier_quota doit voir le nouveau plan/tier — si les users sont chargés depuis la DB à chaque requête, aucun cache à invalider; sinon (cache Redis/session), invalider ou mettre à jour.
- **Tests** : Fixtures free/pro user, mocks Redis; pour 1.7 réutiliser require_admin et ajouter fixtures admin token.
---
## Git Intelligence Summary
- Codebase actuelle : routes admin sous /admin/* (login, logout, users, stats, cleanup, etc.); pas de PATCH users. User avec tier et plan. Tier quota lit le plan pour unlimited. Implémentation 1.7 = ajouter un seul endpoint PATCH et la logique de mise à jour + log audit.
---
## Latest Tech Information
- **FastAPI** : Pour PATCH, utiliser un body Pydantic (e.g. AdminUpdateUserTierRequest avec champ tier ou plan); Path() pour user_id. Réponse 200 avec schema utilisateur (snake_case).
- **Logging** : structlog ou logging standard avec event structuré (admin_tier_change, target_user_id, new_tier, timestamp); pas de contenu document (NFR11).
---
## Project Context Reference
- **Structure** : main.py contient les routes admin; auth via require_admin (Bearer token). GET /admin/users utilise load_users() depuis auth_service. Base de données : database/models.py (User), database/connection.py. Pour modifier un user, il faudra soit étendre auth_service (update_user_plan), soit un service admin dédié qui utilise la même session/DB.
- **Montage** : Ajouter une route PATCH à côté de GET /admin/users; garder le même préfixe (/admin/users ou /api/v1/admin/users selon convention projet).
---
## Story Completion Status
- **Status** : review
- **Note** : Implementation complete; ready for code review.
## Change Log
- 2026-02-20: Story 1.7 created from epics + architecture + previous story 1.6.
- 2026-02-20: Implemented PATCH /admin/users/{user_id}, update_user_plan, audit logging, tests (AC14).
- 2026-02-20: Code review (AI): corrections appliquées — format erreur API (404/400 → `{ error, message, details? }`), schéma Pydantic Literal pour plan, datetime UTC dans repository, tests adaptés.
## Dev Agent Record
### Agent Model Used
{{agent_model_name_version}}
### Debug Log References
### Completion Notes List
- PATCH /admin/users/{user_id} with body `{ "plan": "free"|"starter"|"pro"|"business"|"enterprise" }`; protected by require_admin; 200 with data (id, email, name, plan, tier), 400 invalid plan (INVALID_PLAN), 404 user not found (NOT_FOUND), 401 without/invalid admin token.
- Réponses erreur conformes à larchitecture : 404 → `{ "error": "NOT_FOUND", "message": "User not found" }`; 400 → `{ "error": "INVALID_PLAN", "message": "...", "details": { "allowed": [...] } }` (pas de champ `data`).
- auth_service.update_user_plan(user_id, plan) keeps User.plan and User.tier in sync (tier "pro" for pro/business/enterprise, "free" otherwise); works with JSON and DB backends.
- Audit log: logger.info("admin_tier_change", extra={ event, target_user_id, new_tier, new_plan, admin_id: "admin_session", timestamp ISO8601 }); no document content (AC4).
- Task 2: tier/plan read from DB (or JSON) on each request via get_current_user; no user-tier cache to invalidate.
- Tests: test_admin_tier_change.py (7 tests) — 4.14.5 plus 401 without/invalid token; test 4.2 accepte 400 ou 422 (Literal → 422 pour plan invalide).
- Post-review: AdminUpdateUserTierRequest.plan en Literal; database/repositories.py updated_at en datetime.now(timezone.utc).
### File List
- main.py (PATCH /admin/users/{user_id}, AdminUpdateUserTierRequest avec Literal plan, JSONResponse 404/400 format architecture)
- services/auth_service.py (update_user_plan, VALID_PLAN_VALUES)
- database/repositories.py (updated_at timezone-aware UTC)
- tests/test_admin_tier_change.py (new)
---
## Senior Developer Review (AI)
- **Date :** 2026-02-20
- **Résultat :** Approuvé après correctifs automatiques.
- **Problèmes traités :** Format erreur API (HIGH) — 404/400 retournent désormais `{ error, message, details? }`. Schéma Pydantic avec Literal pour plan (LOW). Datetime UTC dans UserRepository.update (LOW). Tests adaptés pour 400/422 et nouveau format erreur.
- **Recommandation :** Commiter le dossier `tests/` si pas encore versionné (`git add tests/`).