# 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/`).
