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>
164 lines
11 KiB
Markdown
164 lines
11 KiB
Markdown
# 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: 1–4)
|
||
- [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 n’existe 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 l’API 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 l’instant; les routes admin sont dans main.py. Option : ajouter la logique dans main.py ou créer routes/admin_routes.py et l’inclure.
|
||
|
||
### 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 d’un 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 l’utilisateur 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 d’accè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 d’inté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 l’utilisateur; 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 (AC1–4).
|
||
- 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 à l’architecture : 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.1–4.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/`).
|