# ORIGIN_IMPLEMENTATION_TASKS.md ## 📋 RÉSUMÉ EXÉCUTIF Ce document dĂ©finit **2000+ tĂąches atomiques d'implĂ©mentation** de la plateforme Veza. Chaque tĂąche est numĂ©rotĂ©e (T0001 Ă  T2100+), dĂ©taillĂ©e avec code snippets, dĂ©pendances, et Definition of Done. Les tĂąches sont organisĂ©es par phase (1-8) et par module pour permettre une implĂ©mentation systĂ©matique sur 24 mois. ## 🎯 OBJECTIFS ### Objectif Principal Fournir une roadmap d'implĂ©mentation complĂšte, dĂ©taillĂ©e, et atomique permettant Ă  n'importe quel dĂ©veloppeur de travailler de maniĂšre autonome sans ambiguĂŻtĂ©. ### Objectifs Secondaires - Faciliter la planification sprint par sprint - Permettre la parallĂ©lisation des tĂąches - Garantir la traçabilitĂ© feature → tĂąches - Standardiser la qualitĂ© via DoD strict - Optimiser l'estimation (toutes tĂąches < 4h) ## 📊 STATUT D'AVANCEMENT **TĂąches ComplĂ©tĂ©es**: 450/2100+ (21.4%) **DerniĂšre mise Ă  jour**: 2026-03-04 **RĂ©vision Ă©thique**: Modules AI/ML (F456-F470), Web3 (F491-F500), Gamification (F536-F550) supprimĂ©s. TĂąches audit P0/P1 ajoutĂ©es. **Note**: Les tĂąches T0001-T0130 ont Ă©tĂ© archivĂ©es dans `ORIGIN_IMPLEMENTATION_TASKS_ARCHIVE.md` pour rĂ©duire la taille du fichier principal. --- ## 🔧 PHASE 0: ERROR RESOLUTION (PRIORITAIRE) **Statut Global** : 🔄 **EN COURS** **PrioritĂ©** : ⚠ **CRITIQUE - BLOQUE TOUT** **DurĂ©e EstimĂ©e** : 1-2 semaines **PrĂ©requis** : Aucun **Bloque** : Toutes les phases suivantes (Phase 1-8) ### Description Phase de **stabilisation critique** pour corriger **TOUTES** les erreurs existantes dans le codebase actuel avant de reprendre le dĂ©veloppement des 2100+ tĂąches restantes. Cette phase garantit une base de code stable, testable et fonctionnelle. ### Objectifs - ✅ Corriger **100%** des erreurs P0 (critiques bloquantes) - ✅ Corriger **100%** des erreurs P1 (hautes) - ✅ Corriger **≄ 80%** des erreurs P2 (moyennes) - ✅ Documenter toutes les corrections - ✅ Établir une baseline stable pour tests - ✅ Tous les services dĂ©marrent sans erreur - ✅ Tests backend ≄ 80% coverage - ✅ Tests frontend ≄ 80% coverage - ✅ Builds de production rĂ©ussis ### Documentation de RĂ©fĂ©rence - **StratĂ©gie** : `/docs/ORIGIN/ORIGIN_ERROR_RESOLUTION_STRATEGY.md` - **Registre** : `/docs/ORIGIN/ORIGIN_ERROR_REGISTRY.md` - **PrĂ©vention** : `/docs/ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md` ⭐ **NOUVEAU** - **Patterns** : `/docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md` ⭐ **NOUVEAU** - **Standards** : `/docs/ORIGIN/ORIGIN_CODE_STANDARDS.md` - **Architecture** : `/docs/ORIGIN/ORIGIN_MASTER_ARCHITECTURE.md` ### Scripts Utilitaires ```bash # DĂ©couvrir toutes les erreurs existantes ./scripts/discover-errors.sh # GĂ©nĂ©rer un rapport dĂ©taillĂ© ./scripts/generate-error-summary.sh # Voir les logs d'erreur ls -la docs/ORIGIN/error-logs/ ``` ### Workflow AmĂ©liorĂ© ```mermaid graph TD A[Nouvelle TĂąche] --> B{Pre-Flight Check} B -->|FAIL| C[Corriger avant de commencer] B -->|PASS| D[ImplĂ©menter] D --> E[Tests Unitaires] E --> F{Coverage ≄ 80%?} F -->|Non| E F -->|Oui| G[Lint Check] G --> H{Zero Errors?} H -->|Non| D H -->|Oui| I[Commit] I --> J[CI/CD Gates] J --> K{All Gates Pass?} K -->|Non| D K -->|Oui| L[Merge] ``` ### Workflow de Correction (Phase 0) ``` 1. DĂ©couverte → ./scripts/discover-errors.sh 2. Classification → Mettre Ă  jour ORIGIN_ERROR_REGISTRY.md 3. CrĂ©ation tĂąches → CrĂ©er TERR-XXX ci-dessous 4. Correction → Ordre: P0 > P1 > P2 > P3 5. Validation → Tous les services OK + Tests OK 6. Reprise → Continuer Ă  partir de T0511 ``` ### SystĂšme de PrĂ©vention d'Erreurs **NOUVEAU** : Un systĂšme complet de prĂ©vention d'erreurs a Ă©tĂ© mis en place pour Ă©viter la rĂ©apparition des erreurs dans les futures implĂ©mentations. **Avant de commencer TOUTE nouvelle tĂąche** : 1. ✅ **Pre-Flight Check** : ExĂ©cuter `./scripts/pre-flight-check.sh` 2. ✅ **Utiliser Templates** : Copier depuis `dev-environment/templates/` 3. ✅ **Suivre Patterns SĂ»rs** : Consulter `ORIGIN_ERROR_PREVENTION_GUIDE.md` **Documentation** : - **Guide complet** : `/docs/ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md` - **Patterns d'erreurs** : `/docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md` - **Guide rapide** : `/docs/guides/error-prevention-quick-guide.md` **Quality Gates** : - Pre-commit hooks (Husky) : Validation automatique locale - Pre-merge gates (GitHub Actions) : Validation CI/CD bloquante - Voir `.github/workflows/error-prevention.yml` ### TĂąches (TERR-001 Ă  TERR-011) **Note** : Les tĂąches TERR (Task Error Resolution) sont créées selon les erreurs dĂ©couvertes le 2025-11-09. Voir `ORIGIN_ERROR_REGISTRY.md` pour la liste complĂšte et actualisĂ©e. **DĂ©couverte** : `./scripts/discover-errors.sh` (2025-11-09 12:47:15) **Rapport** : `docs/ORIGIN/error-logs/summary-20251109-124715.md` #### Erreurs P0 - Critiques (Bloquent l'application) - 7 tĂąches --- #### TERR-002: Fix Circular Import Cycle in Backend Config/Handlers **CatĂ©gorie**: CAT-01 (Compilation) **PrioritĂ©**: P0 **ComplexitĂ©**: MOYEN **Temps EstimĂ©**: 2-3h **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** Import cyclique dĂ©tectĂ© entre `internal/config`, `internal/handlers`, `internal/services`, crĂ©ant un cycle de dĂ©pendances qui empĂȘche complĂštement la compilation du backend Go. **Message d'Erreur** ``` internal/api/router.go:25:2: package veza-backend-api/internal/api/search is not in std internal/api/router.go:26:2: package veza-backend-api/internal/api/shared_resources is not in std internal/api/router.go:27:2: package veza-backend-api/internal/api/sound_design_contest is not in std internal/api/router.go:28:2: package veza-backend-api/internal/api/tag is not in std internal/api/router.go:29:2: package veza-backend-api/internal/api/track is not in std internal/api/router.go:31:2: package veza-backend-api/internal/api/voting_system is not in std internal/api/api_manager.go:14:2: package veza-backend-api/internal/api/websocket is not in std internal/api/router.go:32:2: package veza-backend-api/internal/core/collaboration is not in std internal/api/api_manager.go:17:2: package veza-backend-api/internal/features is not in std ``` **Cause IdentifiĂ©e** Les packages rĂ©fĂ©rencĂ©s ont Ă©tĂ© planifiĂ©s mais pas encore implĂ©mentĂ©s, ou les imports n'ont pas Ă©tĂ© nettoyĂ©s aprĂšs refactoring. **Solution ProposĂ©e** 1. Analyser chaque import manquant 2. Soit crĂ©er un stub minimal du package si nĂ©cessaire 3. Soit retirer l'import s'il n'est pas utilisĂ© 4. VĂ©rifier que le build passe aprĂšs corrections **Fichiers AffectĂ©s** - `veza-backend-api/internal/api/router.go` - `veza-backend-api/internal/api/api_manager.go` - Potentiellement nouveaux fichiers pour stubs **ImplĂ©mentation** **Étape 1** : Analyser les imports dans router.go et api_manager.go **Étape 2** : Pour chaque package manquant, dĂ©terminer s'il est utilisĂ© **Étape 3** : CrĂ©er des stubs minimaux OU retirer les imports **Étape 4** : Compiler et valider **Tests de Validation** - [ ] `go build ./...` rĂ©ussit sans erreur - [ ] `go test ./...` passe (au moins les tests existants) - [ ] Backend dĂ©marre avec `go run main.go` - [ ] Health check endpoint rĂ©pond - [ ] Aucune rĂ©gression introduite **Definition of Done** - [ ] Backend compile sans erreur de packages manquants - [ ] Tous les imports sont valides - [ ] Tests unitaires passent - [ ] Documentation mise Ă  jour si nouveaux packages créés - [ ] Commit : `TERR-001: fix: Resolve missing backend API packages` --- #### TERR-002: Fix Circular Dependency in internal/config **CatĂ©gorie**: CAT-01 (Compilation) **PrioritĂ©**: P0 **ComplexitĂ©**: MOYEN **Temps EstimĂ©**: 2-3h **Statut**: ⏳ **EN ATTENTE** **Description de l'Erreur** Import cyclique dĂ©tectĂ© entre `internal/config`, `internal/handlers`, et de retour vers `internal/config`. Cela empĂȘche la compilation du backend. **Message d'Erreur** ``` package command-line-arguments imports veza-backend-api/internal/config imports veza-backend-api/internal/handlers imports veza-backend-api/internal/config: import cycle not allowed ``` **Cause IdentifiĂ©e** `config` importe `handlers`, qui Ă  son tour importe `config`, crĂ©ant un cycle de dĂ©pendances. **Solution ProposĂ©e** 1. Identifier les types/fonctions partagĂ©s 2. CrĂ©er un package `internal/types` ou `internal/common` pour les types partagĂ©s 3. Refactorer pour briser le cycle 4. VĂ©rifier que le build passe **Fichiers AffectĂ©s** - `veza-backend-api/internal/config/config.go` - `veza-backend-api/internal/handlers/*.go` - Nouveau: `veza-backend-api/internal/types/types.go` (potentiel) **ImplĂ©mentation** **Étape 1** : Analyser les dĂ©pendances avec `go list -f '{{.ImportPath}} {{.Imports}}'` **Étape 2** : Identifier les types partagĂ©s causant le cycle **Étape 3** : CrĂ©er `internal/types` et y dĂ©placer les types partagĂ©s **Étape 4** : Mettre Ă  jour les imports dans config et handlers **Étape 5** : Compiler et valider **Tests de Validation** - [ ] `go build ./...` rĂ©ussit sans erreur de cycle - [ ] `go test ./...` passe - [ ] Aucun nouveau cycle introduit - [ ] Backend dĂ©marre correctement **Definition of Done** - [ ] Cycle d'import cassĂ© dĂ©finitivement - [ ] Architecture plus propre (sĂ©paration des concerns) - [ ] Tests passent - [ ] Documentation architecture mise Ă  jour - [ ] Commit : `TERR-002: refactor: Break circular dependency in config` --- #### TERR-003: Start Docker Daemon and Enable Service **CatĂ©gorie**: CAT-06 (Docker) **PrioritĂ©**: P0 **ComplexitĂ©**: TRIVIAL **Temps EstimĂ©**: 1min **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** Docker daemon n'est pas en cours d'exĂ©cution sur le systĂšme, empĂȘchant complĂštement le dĂ©marrage de l'infrastructure (PostgreSQL et Redis) via docker-compose. Sans ces services, le backend et les tests ne peuvent pas fonctionner. **Message d'Erreur** ``` Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? ``` **Cause IdentifiĂ©e** Service Docker (dockerd) non dĂ©marrĂ© automatiquement au boot du systĂšme. Le service existe mais n'est pas actif. **Solution ProposĂ©e** 1. DĂ©marrer le service Docker immĂ©diatement 2. Activer le dĂ©marrage automatique au boot 3. VĂ©rifier que le service fonctionne correctement **Fichiers AffectĂ©s** - Aucun fichier de code - Documentation: `docs/guides/DEVELOPMENT_SETUP.md` (Ă  mettre Ă  jour) **ImplĂ©mentation** **Étape 1**: DĂ©marrer Docker ```bash sudo systemctl start docker ``` **Étape 2**: Activer au dĂ©marrage ```bash sudo systemctl enable docker ``` **Étape 3**: VĂ©rifier le statut ```bash sudo systemctl status docker docker ps # Doit fonctionner sans erreur ``` **Étape 4**: Ajouter utilisateur au groupe docker (optionnel, Ă©vite sudo) ```bash sudo usermod -aG docker $USER # Puis se dĂ©connecter/reconnecter ou : newgrp docker ``` **Tests de Validation** - [ ] `sudo systemctl status docker` affiche "active (running)" - [ ] `docker ps` fonctionne sans erreur - [ ] `docker version` affiche client et server - [ ] `docker run hello-world` rĂ©ussit - [ ] Service dĂ©marre automatiquement aprĂšs reboot (optionnel) **Definition of Done** - [ ] Docker daemon en cours d'exĂ©cution - [ ] Docker enabled pour dĂ©marrage automatique - [ ] `docker ps` fonctionne pour l'utilisateur courant - [ ] Documentation `DEVELOPMENT_SETUP.md` mise Ă  jour - [ ] Commit : `TERR-003: fix: Start Docker daemon and enable service` - [ ] PrĂȘt pour TERR-004 (docker-compose) **DĂ©pendances** - BloquĂ© par : Aucune (premiĂšre tĂąche Ă  exĂ©cuter) - Bloque : TERR-004 (docker-compose nĂ©cessite Docker actif) --- #### TERR-004: Fix docker-compose.yml YAML Syntax Error **CatĂ©gorie**: CAT-06 (Docker) **PrioritĂ©**: P0 **ComplexitĂ©**: TRIVIAL **Temps EstimĂ©**: 5min **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** Erreur de syntaxe YAML dans `docker-compose.yml` ligne 60, colonne 102-103. Le parser YAML ne peut pas lire le fichier, empĂȘchant complĂštement l'utilisation de docker-compose pour dĂ©marrer PostgreSQL et Redis. **Message d'Erreur** ``` yaml.scanner.ScannerError: while scanning a block scalar in "./docker-compose.yml", line 60, column 102 expected chomping or indentation indicators, but found '|' in "./docker-compose.yml", line 60, column 103 ``` **Cause IdentifiĂ©e** Syntaxe YAML invalide pour un bloc scalaire. Le caractĂšre `|` (pipe) est utilisĂ© incorrectement, probablement : - Mauvaise indentation avant le `|` - Pipe dupliquĂ© (`||`) - Manque d'espace aprĂšs `key:` - Bloc scalaire mal formĂ© **Solution ProposĂ©e** 1. Lire la ligne 60 du fichier `docker-compose.yml` 2. Identifier l'erreur exacte de syntaxe 3. Corriger selon les rĂšgles YAML 4. Valider avec `docker-compose config` 5. Tester le dĂ©marrage des services **Fichiers AffectĂ©s** - `docker-compose.yml` (ligne 60) **ImplĂ©mentation** **Étape 1**: Examiner la ligne problĂ©matique ```bash sed -n '58,62p' docker-compose.yml # Afficher lignes 58-62 pour contexte ``` **Étape 2**: Identifier l'erreur Types d'erreurs possibles : ```yaml # ❌ MAUVAIS - Pipe mal placĂ© key:| value # ❌ MAUVAIS - Indentation incorrecte key: | value # ❌ MAUVAIS - Pipe dupliquĂ© key: || value # ✅ BON - Syntaxe correcte key: | value multi-line ``` **Étape 3**: Corriger la syntaxe - Assurer 2 espaces d'indentation aprĂšs `key: |` - VĂ©rifier que le contenu du bloc est indentĂ© - Supprimer pipes dupliquĂ©s **Étape 4**: Valider la syntaxe ```bash docker-compose config # Doit rĂ©ussir sans erreur # Ou avec yamllint si installĂ© : yamllint docker-compose.yml ``` **Étape 5**: Tester le dĂ©marrage ```bash docker-compose up -d postgres redis docker-compose ps # VĂ©rifier que les services dĂ©marrent ``` **Tests de Validation** - [ ] `docker-compose config` rĂ©ussit sans erreur - [ ] `docker-compose up -d` dĂ©marre sans erreur - [ ] PostgreSQL dĂ©marre : `docker-compose ps | grep postgres | grep Up` - [ ] Redis dĂ©marre : `docker-compose ps | grep redis | grep Up` - [ ] PostgreSQL accessible : `psql -h localhost -U veza -d veza_db -c "SELECT 1"` - [ ] Redis accessible : `redis-cli ping` retourne "PONG" - [ ] (Optionnel) `yamllint docker-compose.yml` passe **Definition of Done** - [ ] Syntaxe YAML ligne 60 corrigĂ©e - [ ] `docker-compose config` valide le fichier - [ ] Services PostgreSQL et Redis dĂ©marrent - [ ] Services accessibles sur leurs ports respectifs (5432, 6379) - [ ] Aucune autre erreur YAML dĂ©tectĂ©e - [ ] Commit : `TERR-004: fix: Correct YAML syntax error in docker-compose.yml line 60` **DĂ©pendances** - BloquĂ© par : TERR-003 (Docker daemon doit ĂȘtre actif) - Bloque : Infrastructure complĂšte (PostgreSQL, Redis nĂ©cessaires pour backend) --- #### TERR-005: Fix Missing 22+ Packages in Backend API **CatĂ©gorie**: CAT-01 (Compilation) **PrioritĂ©**: P0 **ComplexitĂ©**: COMPLEXE **Temps EstimĂ©**: 4-6h **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** 22+ packages rĂ©fĂ©rencĂ©s dans les imports du backend Go n'existent pas. Ces packages ont Ă©tĂ© planifiĂ©s mais pas encore implĂ©mentĂ©s, ou les imports n'ont pas Ă©tĂ© nettoyĂ©s aprĂšs refactoring. Cela empĂȘche complĂštement la compilation du backend. **Message d'Erreur** ``` internal/api/auth/handler.go:11:2: package veza-backend-api/internal/common is not in std internal/api/auth/handler.go:12:2: package veza-backend-api/internal/response is not in std internal/api/router.go:15:2: package veza-backend-api/internal/api/chat is not in std internal/api/router.go:16:2: package veza-backend-api/internal/api/collaboration is not in std internal/api/router.go:17:2: package veza-backend-api/internal/api/contest is not in std internal/api/api_manager.go:12:2: package veza-backend-api/internal/api/graphql is not in std internal/api/api_manager.go:13:2: package veza-backend-api/internal/api/grpc is not in std internal/api/router.go:20:2: package veza-backend-api/internal/api/listing is not in std internal/api/router.go:21:2: package veza-backend-api/internal/api/message is not in std internal/api/router.go:22:2: package veza-backend-api/internal/api/offer is not in std internal/api/router.go:23:2: package veza-backend-api/internal/api/production_challenge is not in std internal/api/router.go:24:2: package veza-backend-api/internal/api/room is not in std internal/api/router.go:25:2: package veza-backend-api/internal/api/search is not in std internal/api/router.go:26:2: package veza-backend-api/internal/api/shared_resources is not in std internal/api/router.go:27:2: package veza-backend-api/internal/api/sound_design_contest is not in std internal/api/router.go:28:2: package veza-backend-api/internal/api/tag is not in std internal/api/router.go:29:2: package veza-backend-api/internal/api/track is not in std internal/api/user/handler.go:9:2: package veza-backend-api/internal/utils/response is not in std internal/api/router.go:31:2: package veza-backend-api/internal/api/voting_system is not in std internal/api/api_manager.go:14:2: package veza-backend-api/internal/api/websocket is not in std internal/api/router.go:32:2: package veza-backend-api/internal/core/collaboration is not in std internal/api/api_manager.go:17:2: package veza-backend-api/internal/features is not in std ``` **Cause IdentifiĂ©e** Les packages ont Ă©tĂ© planifiĂ©s dans l'architecture mais pas encore créés. Les imports ont Ă©tĂ© ajoutĂ©s en anticipation des features futures, mais le code n'existe pas encore. **Solution ProposĂ©e** Pour chaque package manquant, dĂ©cider entre 2 options : 1. **OPTION A** : Retirer l'import si le package n'est pas utilisĂ© dans le code actuel 2. **OPTION B** : CrĂ©er un stub minimal si le package sera nĂ©cessaire dans les prochaines tĂąches **Fichiers AffectĂ©s** - `veza-backend-api/internal/api/router.go` - `veza-backend-api/internal/api/api_manager.go` - `veza-backend-api/internal/api/auth/handler.go` - `veza-backend-api/internal/api/user/handler.go` - **Potentiellement 22+ nouveaux packages stubs** **ImplĂ©mentation** **Étape 1**: Analyser l'utilisation de chaque import ```bash cd veza-backend-api grep -r "internal/api/chat" internal/api/ # RĂ©pĂ©ter pour chaque package ``` **Étape 2**: Pour chaque package, dĂ©cider : - Si utilisĂ© → CrĂ©er stub minimal - Si non utilisĂ© → Commenter/supprimer l'import **Étape 3**: CrĂ©er stubs minimaux pour packages nĂ©cessaires ```bash # Exemple pour internal/common mkdir -p internal/common cat > internal/common/types.go < tsconfig.json < imports > mocks > tests individuels) **Étape 4**: Valider ```bash npm test # Tous les tests doivent passer npm test -- --coverage # Coverage ≄ 80% ``` **Tests de Validation** - [ ] Tous les tests passent (0 Ă©chec) - [ ] Coverage ≄ 80% (ligne + branche) - [ ] `npm test` exĂ©cution < 5 minutes - [ ] Aucun test flaky (exĂ©cuter 3 fois) - [ ] CI/CD compatible **Definition of Done** - [ ] Tous les tests frontend passent - [ ] Coverage ≄ 80% - [ ] Configuration tests optimisĂ©e - [ ] Tests refactorĂ©s si nĂ©cessaire - [ ] Documentation tests mise Ă  jour - [ ] Commit : `TERR-008: fix: Resolve 4737 frontend test failures` **DĂ©pendances** - BloquĂ© par : TERR-007 (tsconfig.json doit ĂȘtre fixĂ© d'abord) - Bloque : Validation fonctionnelle frontend --- #### TERR-010: Fix Stream Server Rust Build Failed **CatĂ©gorie**: CAT-01 (Compilation) **PrioritĂ©**: P1 **ComplexitĂ©**: MOYEN **Temps EstimĂ©**: 2-4h **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** Stream server Rust ne compile pas. Build failed avec erreurs de compilation (46K de logs). **Message d'Erreur** ``` Build FAILED See: docs/ORIGIN/error-logs/stream-build-20251109-124715.log (46K) ``` **Cause IdentifiĂ©e** À analyser via les logs. Causes probables : - DĂ©pendances manquantes ou versions incompatibles - Erreurs de syntaxe Rust - Features Cargo non activĂ©es - ProblĂšmes de traits ou lifetimes **Solution ProposĂ©e** 1. Analyser les logs de build 2. Identifier les erreurs de compilation 3. Corriger selon les standards Rust 4. Valider avec tests **Fichiers AffectĂ©s** - `veza-stream-server/src/**/*.rs` - `veza-stream-server/Cargo.toml` **ImplĂ©mentation** **Étape 1**: Analyser les erreurs ```bash cd veza-stream-server cat ../docs/ORIGIN/error-logs/stream-build-20251109-124715.log | grep "error\[E" ``` **Étape 2**: Corriger les erreurs par catĂ©gorie **Étape 3**: Valider ```bash cargo build --release cargo test cargo clippy ``` **Tests de Validation** - [ ] `cargo build --release` rĂ©ussit - [ ] `cargo test` passe (tous les tests) - [ ] `cargo clippy` aucun warning critique - [ ] Binaire exĂ©cutable produit - [ ] Service dĂ©marre correctement **Definition of Done** - [ ] Stream server compile sans erreur - [ ] Tests passent - [ ] Clippy OK - [ ] Service dĂ©marre et fonctionne - [ ] Commit : `TERR-010: fix: Resolve stream server build failures` **DĂ©pendances** - BloquĂ© par : Aucune (peut ĂȘtre fait en parallĂšle) - Bloque : FonctionnalitĂ© streaming audio --- #### TERR-011: Fix Chat Server Rust Tests Failed **CatĂ©gorie**: CAT-05 (Tests) **PrioritĂ©**: P1 **ComplexitĂ©**: MOYEN **Temps EstimĂ©**: 2-3h **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** Chat server Rust compile avec succĂšs mais les tests Ă©chouent. **Message d'Erreur** ``` Tests FAILED Build: ✅ OK See: docs/ORIGIN/error-logs/chat-tests-20251109-124715.log (1.3K) ``` **Cause IdentifiĂ©e** Build OK mais tests KO. Causes probables : - Tests obsolĂštes aprĂšs refactoring - Mocks de base de donnĂ©es incorrects - Tests d'intĂ©gration nĂ©cessitant infrastructure - Assertions incorrectes **Solution ProposĂ©e** 1. Analyser les logs de tests 2. Identifier les tests qui Ă©chouent 3. Corriger les tests ou le code 4. Valider que tous les tests passent **Fichiers AffectĂ©s** - `veza-chat-server/tests/**/*.rs` - `veza-chat-server/src/**/*.rs` (si correction code nĂ©cessaire) **ImplĂ©mentation** **Étape 1**: Analyser les Ă©checs ```bash cd veza-chat-server cargo test -- --nocapture 2>&1 | tee test-output.log ``` **Étape 2**: Corriger les tests ou le code **Étape 3**: Valider ```bash cargo test --all-features cargo test -- --ignored # Tests ignorĂ©s ``` **Tests de Validation** - [ ] `cargo test` passe (100% des tests) - [ ] `cargo test --all-features` passe - [ ] Aucun test ignorĂ© sans raison valide - [ ] Coverage ≄ 80% si mesurable **Definition of Done** - [ ] Tous les tests chat server passent - [ ] Aucune rĂ©gression introduite - [ ] Tests refactorĂ©s si nĂ©cessaire - [ ] Commit : `TERR-011: fix: Resolve chat server test failures` **DĂ©pendances** - BloquĂ© par : Aucune (peut ĂȘtre fait en parallĂšle) - Bloque : FonctionnalitĂ© chat validĂ©e --- ### Erreurs P2 - Moyennes (Affectent la qualitĂ© du code) - 1 tĂąche --- #### TERR-009: Fix Frontend Lint Issues (664 errors) **CatĂ©gorie**: CAT-07 (Lint/Format) **PrioritĂ©**: P2 **ComplexitĂ©**: MOYEN **Temps EstimĂ©**: 3-4h **Statut**: ⏳ **EN ATTENTE** **DĂ©couvert**: 2025-11-09 **Description de l'Erreur** 664 erreurs de lint dĂ©tectĂ©es dans le frontend React. Ces erreurs affectent la qualitĂ© et la maintenabilitĂ© du code mais ne bloquent pas la compilation. **Message d'Erreur** ``` 664 lint errors detected See: docs/ORIGIN/error-logs/frontend-lint-20251109-124715.log (168K) ``` **Cause IdentifiĂ©e** Code ne respecte pas les rĂšgles ESLint configurĂ©es. Erreurs probables : - Imports inutilisĂ©s - Variables non utilisĂ©es - ProblĂšmes de formatage - Violations de rĂšgles React/TypeScript **Solution ProposĂ©e** 1. Utiliser `eslint --fix` pour auto-fix 2. Corriger manuellement les erreurs restantes 3. Valider que lint passe sans erreur **Fichiers AffectĂ©s** - `apps/web/src/**/*.tsx` (multiples fichiers) **ImplĂ©mentation** **Étape 1**: Auto-fix les erreurs possibles ```bash cd apps/web npm run lint -- --fix ``` **Étape 2**: Analyser les erreurs restantes ```bash npm run lint > lint-errors.log cat lint-errors.log | grep "error" | cut -d':' -f1 | sort | uniq -c | sort -rn ``` **Étape 3**: Corriger manuellement par catĂ©gorie d'erreur **Étape 4**: Valider ```bash npm run lint # 0 erreur ``` **Tests de Validation** - [ ] `npm run lint` passe (0 erreur) - [ ] Aucune rĂšgle dĂ©sactivĂ©e sans justification - [ ] Code formatĂ© selon Prettier - [ ] Aucune rĂ©gression fonctionnelle **Definition of Done** - [ ] Toutes les erreurs lint corrigĂ©es - [ ] ESLint passe sans erreur ni warning - [ ] Code respecte ORIGIN_CODE_STANDARDS.md - [ ] Commit : `TERR-009: fix: Resolve 664 frontend lint issues` **DĂ©pendances** - BloquĂ© par : TERR-007 (tsconfig), TERR-008 (tests) - Bloque : QualitĂ© du code frontend --- ### Definition of Done de la Phase 0 **CritĂšres de sortie** (tous doivent ĂȘtre ✅) : - [ ] Toutes les erreurs P0 rĂ©solues (100%) - [ ] Toutes les erreurs P1 rĂ©solues (100%) - [ ] Au moins 80% des erreurs P2 rĂ©solues - [ ] Backend Go compile et dĂ©marre sans erreur - [ ] Frontend React compile et dĂ©marre sans erreur - [ ] PostgreSQL et Redis accessibles - [ ] Tests backend ≄ 80% coverage, 100% pass rate - [ ] Tests frontend ≄ 80% coverage, 100% pass rate - [ ] Builds de production (Go + React) rĂ©ussis - [ ] Health checks de tous les services OK - [ ] Aucune erreur critique dans les logs - [ ] ORIGIN_ERROR_REGISTRY.md Ă  jour (toutes erreurs rĂ©solues) - [ ] Documentation mise Ă  jour - [ ] Rapport de validation créé (`docs/ORIGIN/error-logs/validation-report.md`) - [ ] Commit final : `PHASE 0: Error Resolution Complete - Ready for T0511` --- ### Phase 1: Stabilization - ✅ **T0001-T0050**: COMPLÉTÉES (Configuration Management + Testing Infrastructure) - T0050: Add Test Performance Monitoring - T0049: Add Test Data Cleanup Utilities - T0048: Add Test Parallel Execution Helpers - T0047: Add Test Fixtures Generator - T0046: Add Golden File Testing Support - T0045: Add Table-Driven Test Helpers - T0044: Add Benchmark Testing Utilities - T0043: Add Test Coverage Reporting - T0042: Add Mock Helpers for Services - T0041: Add Integration Test Helpers - T0040: Add Configuration Watch Mode - T0039: Add Configuration Environment Detection - T0038: Add Configuration Defaults Builder - T0037: Add Configuration Secrets Management - T0036: Add Configuration Schema Validation - T0035: Add Configuration Testing Utilities - T0034: Add Configuration Hot Reload Support - T0033: Add Configuration Documentation Generator - T0032: Add Environment-Specific Configuration - T0031: Add Configuration Validation - ✅ **T0051-T0072**: COMPLÉTÉES (Chat Server, Stream Server, Frontend) - T0051-T0065: Chat Server Fixes (15 tĂąches) - T0051: Fix Chat Server SQLx Compilation Errors - T0052-T0065: Chat Server autres fixes - T0066-T0069: Stream Server Fixes (4 tĂąches) - T0066: Fix Stream Server WebRTC Configuration - T0067: Add Stream Server Audio Pipeline - T0068: Add Stream Server Connection Pool - T0069: Add Stream Server Environment Configuration - T0070-T0072: Frontend Configuration (3 tĂąches) - T0070: Add Frontend Vite Build Configuration - T0071: Add Frontend Path Aliases Configuration - T0072: Create Frontend Services API Client - ✅ **T0073-T0106**: COMPLÉTÉES (Stream Server, Common Library, Frontend) - T0073-T0080: Stream Server Completion (8 tĂąches) - T0073: Add Stream Server WebSocket Handler - T0074: Add Stream Server Audio Streaming Routes - T0075: Add Stream Server HLS Playlist Generation - T0076: Add Stream Server Graceful Shutdown - T0077: Add Stream Server Health Check Endpoint - T0078: Add Stream Server Metrics Endpoint - T0079: Add Stream Server Error Handling - T0080: Add Stream Server Integration Tests - T0081-T0090: Common Library Setup (10 tĂąches) - T0081: Create Common Library Structure - T0082: Add Common Library Shared Types - T0083: Add Common Library Error Types - T0084: Add Common Library Validation Utilities - T0085: Add Common Library Serialization Helpers - T0086: Add Common Library Date Utilities - T0087: Add Common Library Logging Utilities - T0088: Add Common Library Config Types - T0089: Add Common Library Tests Setup - T0090: Add Common Library Documentation - T0091-T0100: Frontend Build & Structure (10 tĂąches) - T0091: Add Frontend TypeScript Strict Mode - T0092: Add Frontend ESLint Configuration - T0093: Add Frontend Prettier Configuration - T0094: Add Frontend Component Structure - T0095: Add Frontend State Management Setup - T0096: Add Frontend Router Configuration - T0097: Add Frontend Environment Variables Setup - T0098: Add Frontend Error Boundary - T0099: Add Frontend Loading States - T0100: Add Frontend Test Setup - T0101-T0105: Frontend Auth & Pages (5 tĂąches) - ✅ T0101: Add Frontend Authentication Pages - ✅ T0102: Add Frontend Protected Route Component - ✅ T0103: Add Frontend Dashboard Layout - ✅ T0104: Add Frontend Dashboard Page - ✅ T0105: Add Frontend User Profile Page - T0106-T0110: Frontend UI Components (5 tĂąches) - ✅ T0106: Add Frontend Card Component - ✅ T0107: Add Frontend Modal Component - ✅ T0108: Add Frontend Dropdown Component - ✅ T0109: Add Frontend Tooltip Component - ✅ T0110: Add Frontend Dialog Component - T0111-T0115: Frontend Form Components (5 tĂąches) - ✅ T0111: Add Frontend Select Component - ✅ T0112: Add Frontend DatePicker Component - ✅ T0113: Add Frontend FileUpload Component - ✅ T0114: Add Frontend FormBuilder Component - ✅ T0115: Add Frontend Form Validation Utilities - T0116-T0120: Frontend Navigation Components (5 tĂąches) - ✅ T0116: Add Frontend Breadcrumbs Component - ✅ T0117: Add Frontend Tabs Component - ✅ T0118: Add Frontend Pagination Component - ✅ T0119: Add Frontend Search Component - ✅ T0120: Add Frontend Filters Component - ✅ **T0121-T0125**: Frontend Data Display Components (5 tĂąches) - ✅ T0121: Add Frontend Table Component - ✅ T0122: Add Frontend List Component - ✅ T0123: Add Frontend Grid Component - ✅ T0124: Add Frontend Charts Component - ✅ T0125: Add Frontend Timeline Component - ✅ **T0126-T0130**: Frontend Feedback Components (5 tĂąches) - ✅ T0126: Add Frontend Toast/Notification Component - ✅ T0127: Add Frontend Alert Component - ✅ T0128: Add Frontend Progress Component - ✅ T0129: Add Frontend Badge Component - ✅ T0130: Add Frontend Tooltip Advanced Component - ✅ **T0131-T0150**: COMPLÉTÉES (Infrastructure & Docker) - T0131-T0135: Docker Compose Configuration (5 tĂąches) ✅ - T0131: Add Docker Compose for Local Development ✅ - T0132: Add Docker Compose for Production ✅ - T0133: Add Docker Compose for Testing ✅ - T0134: Add Docker Compose Health Checks ✅ - T0135: Add Docker Compose Environment Variables ✅ - T0136-T0140: Dockerfile Optimization (5 tĂąches) ✅ - T0136: Optimize Backend API Dockerfile ✅ - T0137: Optimize Chat Server Dockerfile ✅ - T0138: Optimize Stream Server Dockerfile ✅ - T0139: Optimize Frontend Dockerfile ✅ - T0140: Add .dockerignore Files ✅ - T0141-T0145: CI/CD Pipeline Setup (5 tĂąches) ✅ - T0141: Add GitHub Actions CI Pipeline ✅ - T0142: Add GitHub Actions CD Pipeline ✅ - T0143: Add GitHub Actions Lint Pipeline ✅ - T0144: Add GitHub Actions Security Scan ✅ - T0145: Add GitHub Actions Release Workflow ✅ - T0146-T0150: Deployment Scripts (5 tĂąches) ✅ - T0146: Add Deployment Script for Local Development ✅ - T0147: Add Deployment Script for Production ✅ - T0148: Add Database Migration Script ✅ - T0149: Add Health Check Script ✅ - T0150: Add Logs Collection Script ✅ - ✅ **T0151-T0180**: COMPLÉTÉES (Authentication - User Registration & Login) - T0151-T0155: User Registration Backend (5 tĂąches) ✅ - ✅ T0151: Create User Registration Endpoint - ✅ T0152: Implement Email Validation - ✅ T0153: Implement Password Strength Validation - ✅ T0154: Implement Password Hashing Service - ✅ T0155: Implement User Registration Service - T0156-T0160: User Registration Frontend (5 tĂąches) ✅ - ✅ T0156: Create Registration Form Component - ✅ T0157: Add Email Validation in Frontend - ✅ T0158: Add Password Strength Indicator - ✅ T0159: Add Registration API Integration - ✅ T0160: Add Registration Success Flow - T0161-T0165: Login Backend (5 tĂąches) ✅ - ✅ T0161: Create Login Endpoint - ✅ T0162: Implement Credential Validation - ✅ T0163: Implement JWT Token Generation - ✅ T0164: Implement Refresh Token Management - ✅ T0165: Implement Login Service - T0166-T0170: Login Frontend (5 tĂąches) ✅ - ✅ T0166: Create Login Form Component - ✅ T0167: Add Remember Me Functionality - ✅ T0168: Add Login API Integration - ✅ T0169: Add Token Storage Management - ✅ T0170: Add Login Error Handling - T0171-T0175: JWT Management Backend (5 tĂąches) ✅ - ✅ T0171: Implement JWT Service - ✅ T0172: Implement Token Refresh Endpoint - ✅ T0173: Implement Token Validation Middleware - ✅ T0174: Implement Token Blacklist - ✅ T0175: Implement Token Expiration Handling - T0176-T0180: JWT Management Frontend (5 tĂąches) ✅ - ✅ T0176: Implement Token Refresh Logic - ✅ T0177: Add Automatic Token Refresh - ✅ T0178: Add Token Expiration Handling - ✅ T0179: Add Logout Functionality - ✅ T0180: Add Session Persistence ### Prochaine TĂąche RecommandĂ©e **T0181**: Create Email Verification Token Model --- ## 📖 TABLE DES MATIÈRES 1. [Structure des TĂąches](#1-structure-des-tĂąches) 2. [Phase 1: Stabilization (T0001-T0150)](#2-phase-1-stabilization-t0001-t0150) - [TĂąches ComplĂ©tĂ©es (T0001-T0006)](#tĂąches-complĂ©tĂ©es) - [TĂąches DĂ©tailĂ©es (T0007-T0015)](#tĂąches-dĂ©taillĂ©es) 3. [Phase 2: MVP Core (T0151-T0450)](#3-phase-2-mvp-core-t0151-t0450) 4. [Phase 3: Essential Features (T0451-T0800)](#4-phase-3-essential-features-t0451-t0800) 5. [Phase 4: Marketplace (T0801-T1200)](#5-phase-4-marketplace-t0801-t1200) 6. [Phase 5: Social & Collaboration (T1201-T1500)](#6-phase-5-social--collaboration-t1201-t1500) 7. [Phase 6: Intelligence & Analytics (T1501-T1750)](#7-phase-6-intelligence--analytics-t1501-t1750) 8. [Phase 7: Advanced Monetization (T1751-T1950)](#8-phase-7-advanced-monetization-t1751-t1950) 9. [Phase 8: Scale & Enterprise (T1951-T2100)](#9-phase-8-scale--enterprise-t1951-t2100) ## 🔒 RÈGLES IMMUABLES 1. **ID unique T0001-T2100+** (sĂ©quentiel, pas de gaps) 2. **TĂąche atomique** (30 min - 4h max) 3. **Feature parente** (lien FEAT-XXX-YYY) 4. **DĂ©pendances explicites** (T0XXX) 5. **Code snippets** (Go/Rust/TypeScript) 6. **Tests spĂ©cifiĂ©s** (unit + integration) 7. **DoD strict** (9 critĂšres minimum) 8. **Estimation rĂ©aliste** (rĂ©visĂ©e si dĂ©passĂ©e) 9. **Fichiers prĂ©cis** (chemins complets) 10. **Pas de modification** sans RFC ## 1. STRUCTURE DES TÂCHES ### Format Standard ```markdown ## T{XXXX}: {Titre Court et PrĂ©cis} **Feature Parente**: FEAT-{MODULE}-{NUM} **Phase**: {1-8} **Priority**: critical | high | medium | low **Complexity**: simple | medium | complex **Temps EstimĂ©**: {X}h {Y}min **DĂ©pendances**: T{XXXX}, T{YYYY}, ... ### Description Technique {Description dĂ©taillĂ©e de l'implĂ©mentation} ### Fichiers Ă  CrĂ©er - `chemin/vers/nouveau/fichier.go` - `chemin/vers/nouveau/test.go` ### Fichiers Ă  Modifier - `chemin/vers/fichier/existant.ts` ### ImplĂ©mentation **Étape 1**: {Action prĂ©cise} **Étape 2**: {Action prĂ©cise} **Étape 3**: {Action prĂ©cise} ### Code Snippets **{fichier}.go**: ```go // Code d'exemple ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestFonction(t *testing.T) {} ``` **Integration Tests**: ```go func TestFonctionIntegration(t *testing.T) {} ``` ### Definition of Done - [ ] Code Ă©crit selon standards - [ ] Tests unitaires (coverage ≄ 80%) - [ ] Tests intĂ©gration passent - [ ] Code review (2 approbations) - [ ] Documentation mise Ă  jour - [ ] Pas de warnings linter - [ ] Performance acceptable - [ ] Security scan OK - [ ] DĂ©ployĂ© en staging ``` --- # 2. PHASE 1: STABILIZATION (T0001-T0150) **DurĂ©e**: 1 mois (Janvier 2025) **Objectif**: Fixer bugs critiques, stabiliser base existante **TĂąches**: 150 (T0001-T0150) --- ## T0001: Fix GORM Auto-Migration Warnings ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-001 **Phase**: 1 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 3h 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique RĂ©soudre tous les warnings GORM lors des migrations automatiques. Ajouter les indexes manquants sur les foreign keys, corriger les noms de contraintes, et tester le rollback des migrations. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/database/migrations.go` - `veza-backend-api/internal/database/migrations_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/database/database.go` - `veza-backend-api/internal/models/user.go` - `veza-backend-api/internal/models/track.go` ### ImplĂ©mentation **Étape 1**: Capturer tous les warnings GORM dans les logs **Étape 2**: CrĂ©er fonction `addIndexes()` pour indexes manquants **Étape 3**: Standardiser nommage contraintes (fk_, idx_, chk_) **Étape 4**: Tester migration sur DB vide **Étape 5**: Tester rollback ### Code Snippets **veza-backend-api/internal/database/migrations.go**: ```go package database import ( "fmt" "gorm.io/gorm" "veza/internal/models" ) func RunMigrations(db *gorm.DB) error { // Enable foreign keys if err := db.Exec("PRAGMA foreign_keys = ON").Error; err != nil { return fmt.Errorf("failed to enable foreign keys: %w", err) } // Auto-migrate all models models := []interface{}{ &models.User{}, &models.RefreshToken{}, &models.Track{}, &models.Playlist{}, &models.PlaylistTrack{}, &models.Message{}, &models.Room{}, &models.RoomMember{}, } for _, model := range models { if err := db.AutoMigrate(model); err != nil { return fmt.Errorf("failed to migrate %T: %w", model, err) } } // Add custom indexes if err := addIndexes(db); err != nil { return fmt.Errorf("failed to add indexes: %w", err) } return nil } func addIndexes(db *gorm.DB) error { indexes := []string{ "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email) WHERE deleted_at IS NULL", "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username) WHERE deleted_at IS NULL", "CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id)", "CREATE INDEX IF NOT EXISTS idx_refresh_tokens_token_hash ON refresh_tokens(token_hash)", "CREATE INDEX IF NOT EXISTS idx_tracks_creator_id ON tracks(creator_id)", "CREATE INDEX IF NOT EXISTS idx_tracks_published_at ON tracks(published_at DESC) WHERE published_at IS NOT NULL", "CREATE INDEX IF NOT EXISTS idx_playlists_user_id ON playlists(user_id)", "CREATE INDEX IF NOT EXISTS idx_playlist_tracks_playlist_id ON playlist_tracks(playlist_id, position)", "CREATE INDEX IF NOT EXISTS idx_messages_room_id_created_at ON messages(room_id, created_at DESC)", "CREATE INDEX IF NOT EXISTS idx_room_members_room_id ON room_members(room_id)", "CREATE INDEX IF NOT EXISTS idx_room_members_user_id ON room_members(user_id)", } for _, index := range indexes { if err := db.Exec(index).Error; err != nil { return fmt.Errorf("failed to create index: %w", err) } } return nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestRunMigrations(t *testing.T) { db := setupTestDB() err := RunMigrations(db) assert.NoError(t, err) // Verify tables exist assert.True(t, db.Migrator().HasTable(&models.User{})) assert.True(t, db.Migrator().HasTable(&models.Track{})) } func TestAddIndexes(t *testing.T) { db := setupTestDB() RunMigrations(db) // Verify indexes exist var count int64 db.Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_users_email'").Scan(&count) assert.Equal(t, int64(1), count) } ``` ### Definition of Done - [x] Tous warnings GORM rĂ©solus - [x] Indexes créés sur toutes FK - [x] Nommage contraintes standardisĂ© - [x] Migration testĂ©e sur DB vide - [x] Rollback testĂ© - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© - [x] Documentation mise Ă  jour - [x] DĂ©ployĂ© en staging --- ## T0002: Implement Custom Error Types ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-002 **Phase**: 1 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er systĂšme d'erreurs personnalisĂ©es avec codes d'erreur standardisĂ©s (1000-9999). ImplĂ©menter middleware Gin pour convertir erreurs en rĂ©ponses JSON cohĂ©rentes. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/errors/errors.go` - `veza-backend-api/internal/errors/codes.go` - `veza-backend-api/internal/errors/errors_test.go` - `veza-backend-api/internal/middleware/error_handler.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: DĂ©finir type `AppError` avec code, message, wrapped error **Étape 2**: CrĂ©er constantes pour tous codes d'erreur **Étape 3**: CrĂ©er fonctions helpers (NewValidationError, NewNotFoundError, etc.) **Étape 4**: ImplĂ©menter middleware de conversion erreur → JSON **Étape 5**: Mapper codes erreur → status HTTP ### Code Snippets **veza-backend-api/internal/errors/errors.go**: ```go package errors import "fmt" type ErrorCode int type AppError struct { Code ErrorCode Message string Err error Details []ErrorDetail } type ErrorDetail struct { Field string `json:"field,omitempty"` Message string `json:"message"` } func (e *AppError) Error() string { if e.Err != nil { return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err) } return fmt.Sprintf("[%d] %s", e.Code, e.Message) } func (e *AppError) Unwrap() error { return e.Err } func New(code ErrorCode, message string) *AppError { return &AppError{Code: code, Message: message} } func Wrap(code ErrorCode, message string, err error) *AppError { return &AppError{Code: code, Message: message, Err: err} } func NewValidationError(message string, details ...ErrorDetail) *AppError { return &AppError{ Code: ErrCodeValidation, Message: message, Details: details, } } func NewNotFoundError(resource string) *AppError { return &AppError{ Code: ErrCodeNotFound, Message: fmt.Sprintf("%s not found", resource), } } func NewUnauthorizedError(message string) *AppError { return &AppError{ Code: ErrCodeUnauthorized, Message: message, } } ``` **veza-backend-api/internal/errors/codes.go**: ```go package errors const ( // Authentication & Authorization (1000-1999) ErrCodeInvalidCredentials ErrorCode = 1000 ErrCodeTokenExpired ErrorCode = 1001 ErrCodeTokenInvalid ErrorCode = 1002 ErrCodeForbidden ErrorCode = 1003 ErrCodeUnauthorized ErrorCode = 1002 // Validation (2000-2999) ErrCodeValidation ErrorCode = 2000 ErrCodeRequiredField ErrorCode = 2001 ErrCodeInvalidFormat ErrorCode = 2002 ErrCodeOutOfRange ErrorCode = 2003 // Resource (3000-3999) ErrCodeNotFound ErrorCode = 3000 ErrCodeAlreadyExists ErrorCode = 3001 ErrCodeConflict ErrorCode = 3002 // Business Logic (4000-4999) ErrCodeOperationNotAllowed ErrorCode = 4000 ErrCodeQuotaExceeded ErrorCode = 4005 // Rate Limiting (5000-5099) ErrCodeRateLimitExceeded ErrorCode = 5000 // Internal (9000-9999) ErrCodeInternal ErrorCode = 9000 ErrCodeDatabase ErrorCode = 9001 ) ``` **veza-backend-api/internal/middleware/error_handler.go**: ```go package middleware import ( "github.com/gin-gonic/gin" "veza/internal/errors" ) func ErrorHandler() gin.HandlerFunc { return func(c *gin.Context) { c.Next() if len(c.Errors) > 0 { err := c.Errors.Last().Err if appErr, ok := err.(*errors.AppError); ok { c.JSON(getHTTPStatus(appErr.Code), gin.H{ "error": gin.H{ "code": appErr.Code, "message": appErr.Message, "details": appErr.Details, }, }) return } // Unknown error c.JSON(500, gin.H{ "error": gin.H{ "code": errors.ErrCodeInternal, "message": "Internal server error", }, }) } } } func getHTTPStatus(code errors.ErrorCode) int { switch { case code >= 1000 && code < 2000: if code == errors.ErrCodeForbidden { return 403 } return 401 case code >= 2000 && code < 3000: return 400 case code >= 3000 && code < 4000: if code == errors.ErrCodeNotFound { return 404 } if code == errors.ErrCodeConflict || code == errors.ErrCodeAlreadyExists { return 409 } return 400 case code >= 5000 && code < 6000: return 429 default: return 500 } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestAppError_Error(t *testing.T) { err := errors.New(errors.ErrCodeValidation, "Invalid input") assert.Equal(t, "[2000] Invalid input", err.Error()) } func TestNewValidationError(t *testing.T) { err := errors.NewValidationError("Validation failed", errors.ErrorDetail{Field: "email", Message: "Invalid format"}) assert.Equal(t, errors.ErrCodeValidation, err.Code) assert.Len(t, err.Details, 1) } func TestErrorHandler_Middleware(t *testing.T) { router := gin.New() router.Use(middleware.ErrorHandler()) router.GET("/test", func(c *gin.Context) { c.Error(errors.NewNotFoundError("User")) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 404, w.Code) } ``` ### Definition of Done - [x] Type AppError créé - [x] Codes erreur 1000-9999 dĂ©finis - [x] Fonctions helpers implĂ©mentĂ©es - [x] Middleware error handler créé - [x] Mapping codes → HTTP status - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© - [x] Documentation ajoutĂ©e - [x] Pas de warnings linter --- ## T0003: Fix SQLx Chat Server Compilation ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-001 **Phase**: 1 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique RĂ©soudre erreurs compilation SQLx dans chat server. RĂ©gĂ©nĂ©rer metadata SQLx, aligner queries avec schĂ©ma DB, fixer types Rust. ### Fichiers Ă  CrĂ©er - Aucun ### Fichiers Ă  Modifier - `veza-chat-server/src/repository/message_repository.rs` - `veza-chat-server/src/repository/room_repository.rs` - `veza-chat-server/src/models/message.rs` ### ImplĂ©mentation **Étape 1**: ExĂ©cuter `cargo sqlx prepare --database-url=...` pour rĂ©gĂ©nĂ©rer metadata **Étape 2**: Fixer types dans queries (Uuid pas i32) **Étape 3**: Aligner noms colonnes avec schĂ©ma **Étape 4**: Fixer casting enums PostgreSQL **Étape 5**: Commit `.sqlx/` directory ### Code Snippets **veza-chat-server/src/repository/message_repository.rs**: ```rust use sqlx::{PgPool, Result}; use uuid::Uuid; use chrono::{DateTime, Utc}; use crate::models::{Message, MessageType}; pub struct MessageRepository { pool: PgPool, } impl MessageRepository { pub fn new(pool: PgPool) -> Self { Self { pool } } pub async fn create(&self, room_id: Uuid, sender_id: Uuid, content: &str) -> Result { let message = sqlx::query_as!( Message, r#" INSERT INTO messages (room_id, sender_id, content, message_type, created_at) VALUES ($1, $2, $3, 'text', NOW()) RETURNING id, room_id, sender_id, content, message_type as "message_type: MessageType", created_at, updated_at, deleted_at "#, room_id, sender_id, content ) .fetch_one(&self.pool) .await?; Ok(message) } pub async fn get_room_messages(&self, room_id: Uuid, limit: i64) -> Result> { let messages = sqlx::query_as!( Message, r#" SELECT id, room_id, sender_id, content, message_type as "message_type: MessageType", created_at, updated_at, deleted_at FROM messages WHERE room_id = $1 AND deleted_at IS NULL ORDER BY created_at DESC LIMIT $2 "#, room_id, limit ) .fetch_all(&self.pool) .await?; Ok(messages) } pub async fn delete(&self, id: Uuid) -> Result<()> { sqlx::query!( "UPDATE messages SET deleted_at = NOW() WHERE id = $1", id ) .execute(&self.pool) .await?; Ok(()) } } ``` **veza-chat-server/src/models/message.rs**: ```rust use serde::{Deserialize, Serialize}; use sqlx::Type; use uuid::Uuid; use chrono::{DateTime, Utc}; #[derive(Debug, Clone, Serialize, Deserialize, Type)] #[sqlx(type_name = "message_type", rename_all = "lowercase")] pub enum MessageType { Text, Image, Audio, Video, File, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub id: Uuid, pub room_id: Uuid, pub sender_id: Uuid, pub content: String, pub message_type: MessageType, pub created_at: DateTime, pub updated_at: DateTime, pub deleted_at: Option>, } ``` ### Tests Ă  Écrire **Integration Tests**: ```rust #[tokio::test] async fn test_create_message() { let pool = setup_test_db().await; let repo = MessageRepository::new(pool); let room_id = Uuid::new_v4(); let sender_id = Uuid::new_v4(); let message = repo.create(room_id, sender_id, "Hello world") .await .unwrap(); assert_eq!(message.content, "Hello world"); assert_eq!(message.message_type, MessageType::Text); } #[tokio::test] async fn test_get_room_messages() { let pool = setup_test_db().await; let repo = MessageRepository::new(pool); let room_id = Uuid::new_v4(); let sender_id = Uuid::new_v4(); repo.create(room_id, sender_id, "Message 1").await.unwrap(); repo.create(room_id, sender_id, "Message 2").await.unwrap(); let messages = repo.get_room_messages(room_id, 10).await.unwrap(); assert_eq!(messages.len(), 2); } ``` ### Definition of Done - [x] Toutes erreurs compilation rĂ©solues - [x] SQLx metadata rĂ©gĂ©nĂ©rĂ© - [x] Types alignĂ©s (Uuid, enums) - [x] Queries testĂ©es contre PostgreSQL - [x] Tests intĂ©gration passent - [x] `.sqlx/` commitĂ© - [x] Code review approuvĂ© - [x] cargo build --release OK - [x] DĂ©ployĂ© en staging --- ## T0004: Add Missing Imports Stream Server ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-001 **Phase**: 1 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter imports manquants dans `structured_logging.rs`: HashMap et trace. ### Fichiers Ă  CrĂ©er - Aucun ### Fichiers Ă  Modifier - `veza-stream-server/src/structured_logging.rs` ### ImplĂ©mentation **Étape 1**: Ajouter `use std::collections::HashMap;` **Étape 2**: Ajouter `use tracing::trace;` **Étape 3**: VĂ©rifier compilation **Étape 4**: ExĂ©cuter clippy ### Code Snippets **veza-stream-server/src/structured_logging.rs**: ```rust use std::collections::HashMap; use tracing::{info, warn, error, trace, debug}; use serde_json::json; pub fn log_stream_request( track_id: &str, user_id: &str, bitrate: u32, metadata: HashMap, ) { info!( track_id = track_id, user_id = user_id, bitrate = bitrate, metadata = ?metadata, "Stream request initiated" ); } pub fn trace_audio_chunk(chunk_id: usize, size: usize) { trace!( chunk_id = chunk_id, size = size, "Audio chunk processed" ); } pub fn log_error(error: &str, context: HashMap) { error!( error = error, context = ?context, "Error occurred in stream server" ); } ``` ### Tests Ă  Écrire **Unit Tests**: ```rust #[test] fn test_log_stream_request() { let mut metadata = HashMap::new(); metadata.insert("ip".to_string(), "192.168.1.1".to_string()); // Should not panic log_stream_request("track-123", "user-456", 320, metadata); } #[test] fn test_trace_audio_chunk() { // Should not panic trace_audio_chunk(1, 1024); } ``` ### Definition of Done - [x] Imports ajoutĂ©s - [x] Compilation rĂ©ussie - [x] Pas de warnings clippy - [x] Tests unitaires passent - [x] Code review approuvĂ© - [x] cargo build --release OK - [x] DĂ©ployĂ© en staging --- ## T0005: Configure Vite Path Aliases ✅ COMPLÉTÉE **Feature Parente**: FEAT-UI-001 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer aliases de chemins `@/` dans Vite et TypeScript pour imports frontend. ### Fichiers Ă  CrĂ©er - Aucun ### Fichiers Ă  Modifier - `apps/web/vite.config.ts` - `apps/web/tsconfig.json` ### ImplĂ©mentation **Étape 1**: Ajouter resolve.alias dans vite.config.ts **Étape 2**: Ajouter paths dans tsconfig.json **Étape 3**: Tester import avec @/ **Étape 4**: VĂ©rifier build ### Code Snippets **apps/web/vite.config.ts**: ```typescript import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@features': path.resolve(__dirname, './src/features'), '@services': path.resolve(__dirname, './src/services'), '@hooks': path.resolve(__dirname, './src/hooks'), '@utils': path.resolve(__dirname, './src/utils'), '@types': path.resolve(__dirname, './src/types'), }, }, server: { port: 3000, }, }); ``` **apps/web/tsconfig.json**: ```json { "compilerOptions": { "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "jsx": "react-jsx", "module": "ESNext", "moduleResolution": "bundler", "baseUrl": ".", "paths": { "@/*": ["./src/*"], "@components/*": ["./src/components/*"], "@features/*": ["./src/features/*"], "@services/*": ["./src/services/*"], "@hooks/*": ["./src/hooks/*"], "@utils/*": ["./src/utils/*"], "@types/*": ["./src/types/*"] }, "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true } } ``` ### Tests Ă  Écrire **Manual Tests**: ```typescript // Test import in any component import { Button } from '@/components/ui/Button'; import { useAuth } from '@/hooks/useAuth'; import { api } from '@/services/api'; // Should compile without errors ``` ### Definition of Done - [x] Aliases configurĂ©s dans vite.config.ts - [x] Paths configurĂ©s dans tsconfig.json - [x] Imports avec @/ fonctionnent - [x] Build rĂ©ussi (npm run build) - [x] Tests passent - [x] Code review approuvĂ© - [x] ESLint pas d'erreurs - [x] DĂ©ployĂ© en staging --- ## T0006: Implement JWT Service ✅ COMPLÉTÉE **Feature Parente**: FEAT-AUTH-001 **Phase**: 1 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 3h **DĂ©pendances**: T0002 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX - Coverage: 91.7% ### Description Technique CrĂ©er service JWT pour gĂ©nĂ©ration/validation tokens. Access token 15min, refresh token 7 jours. Inclure user ID, email, role, token version. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/jwt_service.go` - `veza-backend-api/internal/services/jwt_service_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/go.mod` (ajouter github.com/golang-jwt/jwt/v5) ### ImplĂ©mentation **Étape 1**: CrĂ©er struct JWTService avec secretKey **Étape 2**: ImplĂ©menter GenerateAccessToken(user) string, error **Étape 3**: ImplĂ©menter GenerateRefreshToken(user) string, error **Étape 4**: ImplĂ©menter VerifyToken(token) Claims, error **Étape 5**: Ajouter vĂ©rification token version ### Code Snippets **veza-backend-api/internal/services/jwt_service.go**: ```go package services import ( "fmt" "os" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" "veza/internal/models" ) type Claims struct { UserID uuid.UUID `json:"sub"` Email string `json:"email"` Role string `json:"role"` TokenVersion int `json:"token_version"` jwt.RegisteredClaims } type JWTService struct { secretKey []byte } func NewJWTService() *JWTService { secret := os.Getenv("JWT_SECRET") if secret == "" { panic("JWT_SECRET not set") } return &JWTService{secretKey: []byte(secret)} } func (s *JWTService) GenerateAccessToken(user *models.User) (string, error) { claims := Claims{ UserID: user.ID, Email: user.Email, Role: user.Role, TokenVersion: user.TokenVersion, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)), IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: "veza-api", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.secretKey) } func (s *JWTService) GenerateRefreshToken(user *models.User) (string, error) { claims := Claims{ UserID: user.ID, TokenVersion: user.TokenVersion, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), Issuer: "veza-api", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(s.secretKey) } func (s *JWTService) VerifyToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return s.secretKey, nil }) if err != nil { return nil, fmt.Errorf("failed to parse token: %w", err) } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } return nil, fmt.Errorf("invalid token") } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestGenerateAccessToken(t *testing.T) { jwtService := NewJWTService() user := &models.User{ ID: uuid.New(), Email: "test@example.com", Role: "user", TokenVersion: 0, } token, err := jwtService.GenerateAccessToken(user) assert.NoError(t, err) assert.NotEmpty(t, token) claims, err := jwtService.VerifyToken(token) assert.NoError(t, err) assert.Equal(t, user.ID, claims.UserID) } func TestVerifyToken_Expired(t *testing.T) { jwtService := NewJWTService() claims := Claims{ UserID: uuid.New(), RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(-1 * time.Hour)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, _ := token.SignedString(jwtService.secretKey) _, err := jwtService.VerifyToken(tokenString) assert.Error(t, err) } ``` ### Definition of Done - [x] JWTService créé - [x] GenerateAccessToken implĂ©mentĂ© (15min) - [x] GenerateRefreshToken implĂ©mentĂ© (7j) - [x] VerifyToken implĂ©mentĂ© - [x] Token version check - [x] Tests unitaires (coverage ≄ 80%) - [x] Tests expiration - [x] Code review approuvĂ© - [x] Documentation ajoutĂ©e --- ## T0007: Add TokenVersion Field to User Model ✅ COMPLÉTÉE **Feature Parente**: FEAT-AUTH-002 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0001, T0006 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter le champ `TokenVersion` au modĂšle User pour permettre l'invalidation de tous les tokens JWT d'un utilisateur (utile lors d'un changement de mot de passe ou d'une dĂ©connexion forcĂ©e). ### Fichiers Ă  CrĂ©er - Aucun ### Fichiers Ă  Modifier - `veza-backend-api/internal/models/user.go` - `veza-backend-api/internal/services/jwt_service.go` (utiliser user.TokenVersion au lieu de 0) ### ImplĂ©mentation **Étape 1**: Ajouter champ `TokenVersion int` au struct User **Étape 2**: Ajouter tag GORM `gorm:"default:0"` **Étape 3**: Mettre Ă  jour jwt_service.go pour utiliser user.TokenVersion **Étape 4**: CrĂ©er migration pour ajouter colonne en DB **Étape 5**: Mettre Ă  jour tests ### Code Snippets **veza-backend-api/internal/models/user.go**: ```go type User struct { ID int64 `gorm:"primaryKey;autoIncrement" json:"id" db:"id"` Username string `gorm:"not null;uniqueIndex:idx_users_username;size:30" json:"username" db:"username"` Email string `gorm:"not null;uniqueIndex:idx_users_email;size:255" json:"email" db:"email"` PasswordHash string `gorm:"size:255" json:"-" db:"password_hash"` TokenVersion int `gorm:"default:0;not null" json:"token_version" db:"token_version"` // ... autres champs } ``` **veza-backend-api/internal/services/jwt_service.go**: ```go func (s *JWTService) GenerateAccessToken(user *models.User) (string, error) { claims := Claims{ UserID: user.ID, Email: user.Email, Role: user.Role, TokenVersion: user.TokenVersion, // Utiliser le champ du modĂšle // ... } // ... } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestUser_TokenVersion(t *testing.T) { user := &models.User{ ID: 1, TokenVersion: 5, } assert.Equal(t, 5, user.TokenVersion) } func TestJWTService_WithTokenVersion(t *testing.T) { jwtService := setupTestJWTService(t) user := &models.User{ ID: 1, Email: "test@example.com", TokenVersion: 3, } token, err := jwtService.GenerateAccessToken(user) require.NoError(t, err) claims, err := jwtService.VerifyToken(token) require.NoError(t, err) assert.Equal(t, 3, claims.TokenVersion) } ``` ### Definition of Done - [x] TokenVersion ajoutĂ© au modĂšle User - [x] Migration gĂ©rĂ©e par GORM AutoMigrate (automatique) - [x] jwt_service.go utilise user.TokenVersion - [x] Tests unitaires ajoutĂ©s (TestUser_TokenVersion, TestJWTService_WithTokenVersion) - [x] Tous les tests existants mis Ă  jour - [x] Code review approuvĂ© - [x] Documentation mise Ă  jour --- ## T0008: Implement Structured Logging Service ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-003 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX - Coverage: 95.2% ### Description Technique CrĂ©er service de logging structurĂ© avec niveaux (DEBUG, INFO, WARN, ERROR), format JSON pour production, et intĂ©gration avec contexte de requĂȘte (request ID, user ID). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/logging/logger.go` - `veza-backend-api/internal/logging/logger_test.go` - `veza-backend-api/internal/middleware/request_logger.go` ### Fichiers Ă  Modifier - `veza-backend-api/go.mod` (ajouter zap ou logrus) - `veza-backend-api/cmd/api/main.go` ### ImplĂ©mentation **Étape 1**: Ajouter dĂ©pendance zap (uber-go/zap) **Étape 2**: CrĂ©er interface Logger avec mĂ©thodes (Debug, Info, Warn, Error) **Étape 3**: ImplĂ©menter logger structurĂ© avec champs contextuels **Étape 4**: CrĂ©er middleware pour logger requests HTTP **Étape 5**: IntĂ©grer dans main.go ### Code Snippets **veza-backend-api/internal/logging/logger.go**: ```go package logging import ( "go.uber.org/zap" "go.uber.org/zap/zapcore" ) type Logger struct { zap *zap.Logger } func NewLogger(env string) (*Logger, error) { var config zap.Config if env == "production" { config = zap.NewProductionConfig() } else { config = zap.NewDevelopmentConfig() } logger, err := config.Build() if err != nil { return nil, err } return &Logger{zap: logger}, nil } func (l *Logger) Info(msg string, fields ...zap.Field) { l.zap.Info(msg, fields...) } func (l *Logger) Error(msg string, fields ...zap.Field) { l.zap.Error(msg, fields...) } func (l *Logger) With(fields ...zap.Field) *Logger { return &Logger{zap: l.zap.With(fields...)} } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestLogger_Info(t *testing.T) { logger, err := NewLogger("test") require.NoError(t, err) logger.Info("test message", zap.String("key", "value")) // VĂ©rifier que pas de panic } ``` ### Definition of Done - [x] Service logging créé (internal/logging/logger.go) - [x] Interface Logger dĂ©finie avec mĂ©thodes Debug, Info, Warn, Error - [x] Middleware request logger créé (internal/middleware/request_logger.go) - [x] IntĂ©grĂ© dans routes.go (remplace gin.LoggerWithFormatter) - [x] Tests unitaires (coverage: 95.2% > 80% requis) - [x] Format JSON en production, console en dĂ©veloppement - [x] Support pour request ID et user ID dans les logs - [x] Code review approuvĂ© - [x] Documentation ajoutĂ©e --- ## T0009: Create Environment Configuration Service ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-004 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service de configuration centralisĂ© qui charge et valide les variables d'environnement avec valeurs par dĂ©faut et validation des types. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/config.go` - `veza-backend-api/internal/config/config_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct Config avec tous les champs nĂ©cessaires **Étape 2**: ImplĂ©menter Load() pour charger depuis .env **Étape 3**: Ajouter validation des valeurs requises **Étape 4**: Ajouter valeurs par dĂ©faut **Étape 5**: IntĂ©grer dans main.go ### Code Snippets **veza-backend-api/internal/config/config.go**: ```go package config import ( "fmt" "os" "strconv" "github.com/joho/godotenv" ) type Config struct { AppEnv string AppPort int DBHost string DBPort int DBUser string DBPassword string DBName string JWTSecret string RedisURL string } func Load() (*Config, error) { _ = godotenv.Load() config := &Config{ AppEnv: getEnv("APP_ENV", "development"), AppPort: getEnvInt("APP_PORT", 8080), DBHost: getEnv("DB_HOST", "localhost"), DBPort: getEnvInt("DB_PORT", 5432), DBUser: getEnv("DB_USER", "veza"), DBPassword: getEnvRequired("DB_PASSWORD"), DBName: getEnv("DB_NAME", "veza_db"), JWTSecret: getEnvRequired("JWT_SECRET"), RedisURL: getEnv("REDIS_URL", "redis://localhost:6379"), } return config, nil } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func getEnvRequired(key string) string { value := os.Getenv(key) if value == "" { panic(fmt.Sprintf("Required environment variable %s is not set", key)) } return value } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestLoad(t *testing.T) { os.Setenv("DB_PASSWORD", "test") os.Setenv("JWT_SECRET", "secret") config, err := Load() require.NoError(t, err) assert.Equal(t, 8080, config.AppPort) } ``` ### Definition of Done - [x] Struct EnvConfig créé avec tous les champs nĂ©cessaires - [x] Fonction Load() implĂ©mentĂ©e avec chargement depuis .env - [x] Validation des variables requises (getEnvRequired) - [x] Valeurs par dĂ©faut configurĂ©es (AppEnv, AppPort, DBHost, etc.) - [x] Tests unitaires créés (8 tests couvrant tous les cas) - [x] Fonction Load() disponible pour utilisation (package config) - [x] Code review approuvĂ© - [x] Documentation ajoutĂ©e --- ## T0010: Implement Database Connection Pool Management ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-005 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0001, T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer pool de connexions PostgreSQL avec paramĂštres optimisĂ©s (max connections, idle timeout, connection lifetime) et gĂ©rer graceful shutdown. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/database/pool.go` - `veza-backend-api/internal/database/pool_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/database/database.go` - `veza-backend-api/cmd/api/main.go` ### ImplĂ©mentation **Étape 1**: Configurer pool de connexions GORM **Étape 2**: ParamĂ©trer max open connections, max idle, max lifetime **Étape 3**: ImplĂ©menter graceful shutdown **Étape 4**: Ajouter health check endpoint **Étape 5**: Tests de charge ### Code Snippets **veza-backend-api/internal/database/pool.go**: ```go package database import ( "fmt" "time" "gorm.io/driver/postgres" "gorm.io/gorm" "veza-backend-api/internal/config" ) func NewDB(cfg *config.Config) (*gorm.DB, error) { dsn := fmt.Sprintf( "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable", cfg.DBHost, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBPort, ) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return nil, err } sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(25) sqlDB.SetMaxIdleConns(5) sqlDB.SetConnMaxLifetime(5 * time.Minute) return db, nil } func CloseDB(db *gorm.DB) error { sqlDB, err := db.DB() if err != nil { return err } return sqlDB.Close() } ``` ### Tests Ă  Écrire **Integration Tests**: ```go func TestDBPool(t *testing.T) { cfg := &config.Config{/* ... */} db, err := NewDB(cfg) require.NoError(t, err) sqlDB, _ := db.DB() assert.Equal(t, 25, sqlDB.Stats().MaxOpenConnections) } ``` ### Definition of Done - [x] Pool configurĂ© avec paramĂštres optimaux (MaxOpenConns: 25, MaxIdleConns: 5, ConnMaxLifetime: 5min) - [x] Graceful shutdown implĂ©mentĂ© dans database.Close() avec timeout - [x] Health check endpoint créé (utilise IsConnectionHealthy et GetPoolStats) - [x] Tests intĂ©gration créés (9 tests couvrant tous les cas) - [x] Test de performance (100 connexions simultanĂ©es) - [x] Code review approuvĂ© - [x] Documentation ajoutĂ©e --- ## T0011: Add Request ID Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-006 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0008 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er middleware Gin pour gĂ©nĂ©rer un ID unique pour chaque requĂȘte HTTP et l'ajouter au contexte pour traçabilitĂ©. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/request_id.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` ### ImplĂ©mentation **Étape 1**: GĂ©nĂ©rer UUID pour chaque requĂȘte **Étape 2**: Ajouter header X-Request-ID **Étape 3**: Stocker dans contexte Gin **Étape 4**: Utiliser dans logger ### Code Snippets **veza-backend-api/internal/middleware/request_id.go**: ```go package middleware import ( "github.com/gin-gonic/gin" "github.com/google/uuid" ) func RequestID() gin.HandlerFunc { return func(c *gin.Context) { requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() } c.Set("request_id", requestID) c.Header("X-Request-ID", requestID) c.Next() } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestRequestID(t *testing.T) { router := gin.New() router.Use(RequestID()) router.GET("/test", func(c *gin.Context) { requestID, _ := c.Get("request_id") c.JSON(200, gin.H{"request_id": requestID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.NotEmpty(t, w.Header().Get("X-Request-ID")) } ``` ### Definition of Done - [x] Middleware RequestID créé (internal/middleware/request_id.go) - [x] UUID gĂ©nĂ©rĂ© pour chaque requĂȘte (v4 via google/uuid) - [x] Header X-Request-ID ajoutĂ© Ă  toutes les rĂ©ponses - [x] IntĂ©grĂ© avec logger (utilisĂ© par RequestLogger) - [x] Tests unitaires créés (6 tests couvrant tous les cas) - [x] IntĂ©grĂ© dans SetupMiddleware (premiĂšre position) - [x] Code review approuvĂ© --- ## T0012: Implement Health Check Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-007 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0010 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint `/health` qui vĂ©rifie l'Ă©tat de la DB, Redis, et retourne status OK/degraded/down. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/health.go` - `veza-backend-api/internal/handlers/health_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` (ajouter route) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler HealthCheck **Étape 2**: VĂ©rifier connexion DB (ping) **Étape 3**: VĂ©rifier connexion Redis (optionnel) **Étape 4**: Retourner JSON avec status **Étape 5**: Route GET /health ### Code Snippets **veza-backend-api/internal/handlers/health.go**: ```go package handlers import ( "time" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type HealthHandler struct { db *gorm.DB } func NewHealthHandler(db *gorm.DB) *HealthHandler { return &HealthHandler{db: db} } func (h *HealthHandler) Check(c *gin.Context) { sqlDB, err := h.db.DB() dbStatus := "up" if err != nil || sqlDB.Ping() != nil { dbStatus = "down" } status := "ok" if dbStatus == "down" { status = "degraded" } c.JSON(200, gin.H{ "status": status, "database": dbStatus, "timestamp": time.Now().Unix(), }) } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestHealthCheck(t *testing.T) { db := setupTestDB() handler := NewHealthHandler(db) router := gin.New() router.GET("/health", handler.Check) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/health", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "ok") } ``` ### Definition of Done - [x] Endpoint /health créé (route GET /api/v1/health) - [x] VĂ©rification DB implĂ©mentĂ©e (ping avec gestion d'erreurs) - [x] Retourne status appropriĂ© (ok/degraded selon Ă©tat DB) - [x] Tests unitaires créés (7 tests couvrant tous les cas) - [x] IntĂ©grĂ© dans config et routes - [x] Code review approuvĂ© --- ## T0013: Create Test Utilities Package ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-008 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0010 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er package `testutils` avec fonctions helpers pour setup DB de test, fixtures, cleanup, etc. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/db.go` - `veza-backend-api/internal/testutils/fixtures.go` ### Fichiers Ă  Modifier - Aucun (nouveau package) ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction SetupTestDB() **Étape 2**: CrĂ©er fonction CleanupTestDB() **Étape 3**: CrĂ©er fixtures pour User, Track, etc. **Étape 4**: Helper pour crĂ©er donnĂ©es de test **Étape 5**: Exemples d'utilisation ### Code Snippets **veza-backend-api/internal/testutils/db.go**: ```go package testutils import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "veza-backend-api/internal/models" ) func SetupTestDB() *gorm.DB { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { panic(err) } db.AutoMigrate( &models.User{}, &models.Track{}, // ... autres modĂšles ) return db } func CleanupTestDB(db *gorm.DB) { sqlDB, _ := db.DB() sqlDB.Close() } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestSetupTestDB(t *testing.T) { db := SetupTestDB() defer CleanupTestDB(db) assert.True(t, db.Migrator().HasTable(&models.User{})) } ``` ### Definition of Done - [x] Package testutils créé (internal/testutils/) - [x] SetupTestDB() implĂ©mentĂ© avec SQLite en mĂ©moire - [x] CleanupTestDB() et ResetTestDB() implĂ©mentĂ©s - [x] Fixtures créées (User, Track, Playlist, Room, Message) - [x] Tests unitaires créés (17 tests, coverage 71.4%) - [x] Documentation avec exemples (README.md) - [x] Code review approuvĂ© --- ## T0014: Implement CORS Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-009 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer middleware CORS pour permettre requĂȘtes cross-origin depuis le frontend avec whitelist d'origins configurable. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/cors.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: Ajouter dĂ©pendance gin-cors ou implĂ©menter manuellement **Étape 2**: Configurer allowed origins depuis config **Étape 3**: Permettre mĂ©thodes GET, POST, PUT, DELETE **Étape 4**: Permettre headers Authorization, Content-Type **Étape 5**: Tests avec diffĂ©rentes origins ### Code Snippets **veza-backend-api/internal/middleware/cors.go**: ```go package middleware import ( "github.com/gin-gonic/gin" "strings" ) func CORS(allowedOrigins []string) gin.HandlerFunc { return func(c *gin.Context) { origin := c.GetHeader("Origin") if isAllowedOrigin(origin, allowedOrigins) { c.Header("Access-Control-Allow-Origin", origin) } c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type") c.Header("Access-Control-Allow-Credentials", "true") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } func isAllowedOrigin(origin string, allowed []string) bool { for _, o := range allowed { if o == "*" || o == origin { return true } } return false } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestCORS(t *testing.T) { router := gin.New() router.Use(CORS([]string{"http://localhost:3000"})) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.Header.Set("Origin", "http://localhost:3000") router.ServeHTTP(w, req) assert.Equal(t, "http://localhost:3000", w.Header().Get("Access-Control-Allow-Origin")) } ``` ### Definition of Done - [x] Middleware CORS créé avec whitelist configurable - [x] Whitelist d'origins configurable (variable d'environnement CORS_ALLOWED_ORIGINS) - [x] Headers et mĂ©thodes configurĂ©s (GET, POST, PUT, DELETE, OPTIONS) - [x] Tests unitaires créés (9 tests, coverage > 90% pour CORS) - [x] IntĂ©grĂ© dans config.go et routes.go - [x] Support wildcard "*" pour toutes les origines - [x] Code review approuvĂ© --- ## T0015: Add Rate Limiting Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-010 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune (optionnel: Redis pour distribuĂ©) **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter rate limiting par IP avec limite configurable (ex: 100 req/min) et retourner 429 Too Many Requests. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/ratelimit.go` - `veza-backend-api/internal/middleware/ratelimit_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct RateLimiter avec map IP → count **Étape 2**: ImplĂ©menter middleware avec window sliding **Étape 3**: Ajouter headers X-RateLimit-* **Étape 4**: Configurer limites dans config **Étape 5**: Tests avec multiples requĂȘtes ### Code Snippets **veza-backend-api/internal/middleware/ratelimit.go**: ```go package middleware import ( "strconv" "sync" "time" "github.com/gin-gonic/gin" ) type RateLimiter struct { requests map[string][]time.Time limit int window time.Duration mu sync.Mutex } func NewRateLimiter(limit int, window time.Duration) *RateLimiter { rl := &RateLimiter{ requests: make(map[string][]time.Time), limit: limit, window: window, } go rl.cleanup() return rl } func (rl *RateLimiter) Middleware() gin.HandlerFunc { return func(c *gin.Context) { ip := c.ClientIP() rl.mu.Lock() now := time.Now() cutoff := now.Add(-rl.window) // Clean old requests valid := []time.Time{} for _, t := range rl.requests[ip] { if t.After(cutoff) { valid = append(valid, t) } } if len(valid) >= rl.limit { rl.mu.Unlock() c.Header("X-RateLimit-Limit", strconv.Itoa(rl.limit)) c.Header("X-RateLimit-Remaining", "0") c.AbortWithStatus(429) return } valid = append(valid, now) rl.requests[ip] = valid remaining := rl.limit - len(valid) rl.mu.Unlock() c.Header("X-RateLimit-Limit", strconv.Itoa(rl.limit)) c.Header("X-RateLimit-Remaining", strconv.Itoa(remaining)) c.Next() } } func (rl *RateLimiter) cleanup() { ticker := time.NewTicker(1 * time.Minute) for range ticker.C { rl.mu.Lock() cutoff := time.Now().Add(-rl.window) for ip, times := range rl.requests { valid := []time.Time{} for _, t := range times { if t.After(cutoff) { valid = append(valid, t) } } if len(valid) == 0 { delete(rl.requests, ip) } else { rl.requests[ip] = valid } } rl.mu.Unlock() } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestRateLimiter(t *testing.T) { limiter := NewRateLimiter(5, 1*time.Minute) router := gin.New() router.Use(limiter.Middleware()) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) // Faire 6 requĂȘtes for i := 0; i < 5; i++ { w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.RemoteAddr = "127.0.0.1:12345" router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) } // 6Ăšme devrait ĂȘtre bloquĂ©e w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) req.RemoteAddr = "127.0.0.1:12345" router.ServeHTTP(w, req) assert.Equal(t, 429, w.Code) } ``` ### Definition of Done - [x] Middleware rate limiting créé (SimpleRateLimiter avec sliding window) - [x] Limite par IP implĂ©mentĂ©e (map IP → timestamps) - [x] Headers X-RateLimit-* ajoutĂ©s (Limit, Remaining, Reset) - [x] Tests unitaires créés (8 tests, coverage > 85%) - [x] Configurable via config (RATE_LIMIT_LIMIT, RATE_LIMIT_WINDOW) - [x] Cleanup automatique des anciennes requĂȘtes - [x] Code review approuvĂ© --- ## T0016: Implement Error Response Standardization ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-011 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0002 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er middleware Gin pour standardiser toutes les rĂ©ponses d'erreur au format JSON cohĂ©rent avec codes d'erreur et messages structurĂ©s. ### Fichiers Ă  CrĂ©er - Aucun (utiliser middleware existant) ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/error_handler.go` - `veza-backend-api/internal/routes/routes.go` ### ImplĂ©mentation **Étape 1**: VĂ©rifier que ErrorHandler middleware existe et fonctionne **Étape 2**: Standardiser format de rĂ©ponse (code, message, details) **Étape 3**: Mapper tous les types d'erreurs (GORM, validation, custom) **Étape 4**: IntĂ©grer dans SetupMiddleware **Étape 5**: Tests avec diffĂ©rents types d'erreurs ### Code Snippets **veza-backend-api/internal/middleware/error_handler.go**: ```go package middleware import ( "net/http" "github.com/gin-gonic/gin" "go.uber.org/zap" "gorm.io/gorm" "veza-backend-api/internal/errors" ) // ErrorHandler middleware pour gĂ©rer toutes les erreurs de maniĂšre standardisĂ©e func ErrorHandler(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { c.Next() // Traiter les erreurs stockĂ©es dans le contexte if len(c.Errors) > 0 { err := c.Errors.Last().Err // VĂ©rifier si c'est une AppError personnalisĂ©e if appErr, ok := err.(*errors.AppError); ok { httpStatus := mapErrorCodeToHTTPStatus(appErr.Code) logger.Error("Application error", zap.Int("code", int(appErr.Code)), zap.String("message", appErr.Message), zap.Int("http_status", httpStatus), ) c.JSON(httpStatus, gin.H{ "error": gin.H{ "code": appErr.Code, "message": appErr.Message, "details": appErr.Details, }, }) return } // VĂ©rifier si c'est une erreur GORM if err == gorm.ErrRecordNotFound { logger.Warn("Record not found", zap.Error(err)) c.JSON(http.StatusNotFound, gin.H{ "error": gin.H{ "code": errors.ErrCodeNotFound, "message": "Resource not found", }, }) return } // Erreur gĂ©nĂ©rique logger.Error("Internal server error", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{ "error": gin.H{ "code": errors.ErrCodeInternal, "message": "Internal server error", }, }) } } } // mapErrorCodeToHTTPStatus convertit un code d'erreur en status HTTP func mapErrorCodeToHTTPStatus(code errors.ErrorCode) int { switch { case code >= 1000 && code < 2000: if code == errors.ErrCodeForbidden { return http.StatusForbidden } return http.StatusUnauthorized case code >= 2000 && code < 3000: return http.StatusBadRequest case code >= 3000 && code < 4000: if code == errors.ErrCodeNotFound { return http.StatusNotFound } if code == errors.ErrCodeConflict || code == errors.ErrCodeAlreadyExists { return http.StatusConflict } return http.StatusBadRequest case code >= 5000 && code < 6000: return http.StatusTooManyRequests default: return http.StatusInternalServerError } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestErrorHandler_AppError(t *testing.T) { logger := zap.NewNop() router := gin.New() router.Use(ErrorHandler(logger)) router.GET("/test", func(c *gin.Context) { c.Error(errors.NewNotFoundError("User")) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) assert.Contains(t, w.Body.String(), "not found") } func TestErrorHandler_GORMError(t *testing.T) { logger := zap.NewNop() router := gin.New() router.Use(ErrorHandler(logger)) router.GET("/test", func(c *gin.Context) { c.Error(gorm.ErrRecordNotFound) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } ``` ### Definition of Done - [x] ErrorHandler middleware standardise toutes les erreurs - [x] Format JSON cohĂ©rent pour toutes les erreurs (code, message, details) - [x] Mapping AppError → HTTP status (mapErrorCodeToHTTPStatus) - [x] Gestion des erreurs GORM (RecordNotFound → 404) - [x] Logging structurĂ© avec zap (Error/Warn selon type) - [x] Tests unitaires créés (8 tests, coverage > 85%) - [x] IntĂ©grĂ© dans routes.go (dernier middleware pour capturer toutes les erreurs) - [x] Code review approuvĂ© --- ## T0017: Add Error Context Propagation ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-012 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0002 ✅, T0011 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique AmĂ©liorer propagation du contexte d'erreur (request ID, user ID, stack trace) pour faciliter le debugging en production. ### Fichiers Ă  CrĂ©er - Aucun ### Fichiers Ă  Modifier - `veza-backend-api/internal/errors/errors.go` - `veza-backend-api/internal/middleware/error_handler.go` ### ImplĂ©mentation **Étape 1**: Ajouter champ Context Ă  AppError **Étape 2**: Enrichir erreurs avec request_id depuis contexte **Étape 3**: Ajouter user_id si disponible **Étape 4**: Logger stack trace en mode debug **Étape 5**: Tests de propagation contexte ### Code Snippets **veza-backend-api/internal/errors/errors.go**: ```go type AppError struct { Code ErrorCode Message string Err error Details []ErrorDetail Context map[string]interface{} // Contexte additionnel (request_id, user_id, etc.) } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestAppError_WithContext(t *testing.T) { err := errors.New(errors.ErrCodeValidation, "Invalid input") err.Context = map[string]interface{}{ "request_id": "abc123", "user_id": 42, } assert.NotNil(t, err.Context) assert.Equal(t, "abc123", err.Context["request_id"]) } ``` ### Definition of Done - [x] Champ Context ajoutĂ© Ă  AppError (map[string]interface{}) - [x] Request ID propagĂ© automatiquement depuis contexte Gin - [x] User ID propagĂ© si disponible dans contexte Gin - [x] Enrichissement automatique dans ErrorHandler (enrichErrorWithContext) - [x] Contexte inclus dans rĂ©ponse JSON (champ "context") - [x] Contexte inclus dans logs structurĂ©s (zap fields) - [x] Tests unitaires créés (7 tests errors + 5 tests middleware, coverage > 85%) - [x] Code review approuvĂ© --- ## T0018: Implement Validation Error Helpers ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-013 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0002 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er fonctions helpers pour gĂ©nĂ©rer des erreurs de validation structurĂ©es depuis validators (go-playground/validator). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/errors/validation.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction FromValidatorError(validator.ValidationErrors) **Étape 2**: Mapper chaque erreur de validation en ErrorDetail **Étape 3**: Extraire tag, field, message **Étape 4**: Retourner AppError avec details **Étape 5**: Tests avec validator errors ### Code Snippets **veza-backend-api/internal/errors/validation.go**: ```go package errors import ( "github.com/go-playground/validator/v10" ) // FromValidatorError convertit une erreur de validation en AppError func FromValidatorError(err error) *AppError { if validationErrors, ok := err.(validator.ValidationErrors); ok { details := make([]ErrorDetail, 0, len(validationErrors)) for _, fieldError := range validationErrors { details = append(details, ErrorDetail{ Field: fieldError.Field(), Message: getValidationMessage(fieldError), }) } return &AppError{ Code: ErrCodeValidation, Message: "Validation failed", Details: details, } } return New(ErrCodeValidation, err.Error()) } func getValidationMessage(fieldError validator.FieldError) string { switch fieldError.Tag() { case "required": return fieldError.Field() + " is required" case "email": return fieldError.Field() + " must be a valid email" case "min": return fieldError.Field() + " must be at least " + fieldError.Param() case "max": return fieldError.Field() + " must be at most " + fieldError.Param() default: return fieldError.Field() + " is invalid" } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestFromValidatorError(t *testing.T) { validate := validator.New() type TestStruct struct { Email string `validate:"required,email"` Age int `validate:"min=18"` } s := TestStruct{Email: "invalid", Age: 15} err := validate.Struct(s) appErr := errors.FromValidatorError(err) assert.Equal(t, errors.ErrCodeValidation, appErr.Code) assert.Greater(t, len(appErr.Details), 0) } ``` ### Definition of Done - [x] FromValidatorError implĂ©mentĂ© (convertit validator.ValidationErrors → AppError) - [x] Mapping complet des tags de validation (required, email, min, max, len, gte, lte, gt, lt, url, alphanum, alpha, numeric, oneof) - [x] Messages d'erreur lisibles et contextuels - [x] Support pour erreurs multiples (un ErrorDetail par champ invalide) - [x] Tests unitaires créés (9 tests, coverage > 90%) - [x] Code review approuvĂ© --- ## T0019: Add Error Recovery Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-014 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0008 ✅, T0016 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Renforcer middleware de rĂ©cupĂ©ration d'erreurs Gin pour capturer les panics et les logger correctement avec contexte. ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/recovery.go` (ou crĂ©er) ### ImplĂ©mentation **Étape 1**: CrĂ©er Recovery middleware avec logger **Étape 2**: Capturer panic avec stack trace **Étape 3**: Logger avec request_id et contexte **Étape 4**: Retourner erreur 500 standardisĂ©e **Étape 5**: Remplacer gin.Recovery() dans routes ### Code Snippets **veza-backend-api/internal/middleware/recovery.go**: ```go package middleware import ( "net/http" "runtime/debug" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // Recovery middleware personnalisĂ© avec logging structurĂ© func Recovery(logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { requestID, _ := c.Get("request_id") stack := debug.Stack() logger.Error("Panic recovered", zap.Any("error", err), zap.String("request_id", requestID.(string)), zap.String("path", c.Request.URL.Path), zap.String("method", c.Request.Method), zap.ByteString("stack", stack), ) c.JSON(http.StatusInternalServerError, gin.H{ "error": gin.H{ "code": 9000, "message": "Internal server error", }, }) c.Abort() } }() c.Next() } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestRecovery(t *testing.T) { logger := zap.NewNop() router := gin.New() router.Use(Recovery(logger)) router.GET("/test", func(c *gin.Context) { panic("test panic") }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusInternalServerError, w.Code) } ``` ### Definition of Done - [x] Recovery middleware créé avec logging structurĂ© (zap) - [x] Stack trace capturĂ© et loggĂ© (runtime/debug.Stack()) - [x] Request ID inclus dans logs (depuis contexte Gin) - [x] User ID inclus dans logs si disponible - [x] Contexte complet loggĂ© (path, method, stack trace) - [x] Tests unitaires créés (7 tests, coverage > 90%) - [x] Remplacer gin.Recovery() dans routes.go - [x] Code review approuvĂ© --- ## T0020: Implement Error Metrics Collection ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-015 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0016 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter collecte de mĂ©triques d'erreurs (compteurs par type d'erreur, codes HTTP) pour monitoring. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/metrics/errors.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/error_handler.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er package metrics avec compteurs **Étape 2**: Compter erreurs par code (404, 500, etc.) **Étape 3**: Compter erreurs par type (validation, not found, etc.) **Étape 4**: IntĂ©grer dans ErrorHandler **Étape 5**: Tests de comptage ### Code Snippets **veza-backend-api/internal/metrics/errors.go**: ```go package metrics import ( "sync" "veza-backend-api/internal/errors" ) type ErrorMetrics struct { mu sync.RWMutex errorsByCode map[errors.ErrorCode]int64 errorsByHTTPStatus map[int]int64 totalErrors int64 } func NewErrorMetrics() *ErrorMetrics { return &ErrorMetrics{ errorsByCode: make(map[errors.ErrorCode]int64), errorsByHTTPStatus: make(map[int]int64), } } func (m *ErrorMetrics) RecordError(code errors.ErrorCode, httpStatus int) { m.mu.Lock() defer m.mu.Unlock() m.errorsByCode[code]++ m.errorsByHTTPStatus[httpStatus]++ m.totalErrors++ } func (m *ErrorMetrics) GetStats() map[string]interface{} { m.mu.RLock() defer m.mu.RUnlock() return map[string]interface{}{ "total_errors": m.totalErrors, "errors_by_code": m.errorsByCode, "errors_by_http_status": m.errorsByHTTPStatus, } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestErrorMetrics_RecordError(t *testing.T) { metrics := NewErrorMetrics() metrics.RecordError(errors.ErrCodeNotFound, 404) metrics.RecordError(errors.ErrCodeValidation, 400) stats := metrics.GetStats() assert.Equal(t, int64(2), stats["total_errors"]) } ``` ### Definition of Done - [x] ErrorMetrics créé avec thread-safe (mutex) - [x] Compteurs par code d'erreur (errorsByCode) - [x] Compteurs par status HTTP (errorsByHTTPStatus) - [x] Compteur total d'erreurs (totalErrors) - [x] IntĂ©grĂ© dans ErrorHandler (T0020) - [x] IntĂ©grĂ© dans config.go (initialisation) - [x] Tests unitaires créés (15 tests au total, coverage > 90%) - [x] Tests d'intĂ©gration avec ErrorHandler (5 tests) - [x] Support nil metrics (pas de panique si metrics non initialisĂ©) - [x] Code review approuvĂ© --- ## T0021: Expose Prometheus Metrics Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-016 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0020 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Exposer endpoint `/metrics` compatible Prometheus pour exporter les mĂ©triques d'erreurs et autres mĂ©triques systĂšme. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/metrics/prometheus.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/metrics.go` (ou crĂ©er) - `veza-backend-api/internal/routes/routes.go` ### ImplĂ©mentation **Étape 1**: Ajouter dĂ©pendance prometheus/client_golang **Étape 2**: CrĂ©er registry Prometheus **Étape 3**: Exposer ErrorMetrics via Prometheus **Étape 4**: CrĂ©er endpoint /metrics **Étape 5**: Tests de format Prometheus ### Code Snippets **veza-backend-api/internal/metrics/prometheus.go**: ```go package metrics import ( "strconv" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "veza-backend-api/internal/errors" ) var ( errorsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "veza_errors_total", Help: "Total number of errors by code and HTTP status", }, []string{"error_code", "http_status"}, ) errorsByCode = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "veza_errors_by_code_total", Help: "Total number of errors by error code", }, []string{"error_code"}, ) errorsByHTTPStatus = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "veza_errors_by_http_status_total", Help: "Total number of errors by HTTP status code", }, []string{"http_status"}, ) ) // RecordErrorPrometheus enregistre une erreur dans Prometheus func RecordErrorPrometheus(code errors.ErrorCode, httpStatus int) { codeStr := strconv.Itoa(int(code)) statusStr := strconv.Itoa(httpStatus) errorsTotal.WithLabelValues(codeStr, statusStr).Inc() errorsByCode.WithLabelValues(codeStr).Inc() errorsByHTTPStatus.WithLabelValues(statusStr).Inc() } ``` **veza-backend-api/internal/handlers/metrics.go**: ```go package handlers import ( "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" ) // PrometheusMetrics expose les mĂ©triques Prometheus func PrometheusMetrics() gin.HandlerFunc { h := promhttp.Handler() return func(c *gin.Context) { h.ServeHTTP(c.Writer, c.Request) } } ``` ### Tests Ă  Écrire **Integration Tests**: ```go func TestPrometheusMetricsEndpoint(t *testing.T) { router := gin.New() router.GET("/metrics", handlers.PrometheusMetrics()) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/metrics", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Body.String(), "veza_errors_total") } ``` ### Definition of Done - [x] DĂ©pendance prometheus/client_golang ajoutĂ©e (prometheus, promauto, promhttp) - [x] MĂ©triques Prometheus créées (errorsTotal, errorsByCode, errorsByHTTPStatus) - [x] ErrorMetrics exposĂ© via Prometheus (RecordErrorPrometheus) - [x] Endpoint /metrics créé (route GET /api/v1/metrics) - [x] Handler PrometheusMetrics() créé - [x] IntĂ©grĂ© dans ErrorHandler (3 points d'enregistrement) - [x] Tests unitaires créés (4 tests metrics, 4 tests handler) - [x] Tests d'intĂ©gration (coverage > 85%) - [x] Format Prometheus valide (text/plain avec # HELP, # TYPE) - [x] Code review approuvĂ© --- ## T0022: Add HTTP Request Metrics Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-017 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0021 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er middleware pour collecter mĂ©triques HTTP (request duration, count, status codes) et les exposer via Prometheus. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/metrics.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/routes/routes.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er mĂ©triques Prometheus (http_requests_total, http_request_duration_seconds) **Étape 2**: Middleware pour capturer durĂ©e et status **Étape 3**: Labels: method, path, status **Étape 4**: IntĂ©grer dans SetupMiddleware **Étape 5**: Tests de mĂ©triques ### Code Snippets **veza-backend-api/internal/middleware/metrics.go**: ```go package middleware import ( "strconv" "time" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) var ( httpRequestsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "veza_http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "path", "status"}, ) httpRequestDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "veza_http_request_duration_seconds", Help: "HTTP request duration in seconds", Buckets: prometheus.DefBuckets, }, []string{"method", "path", "status"}, ) ) // Metrics middleware pour collecter mĂ©triques HTTP func Metrics() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.FullPath() if path == "" { path = c.Request.URL.Path } c.Next() duration := time.Since(start).Seconds() status := strconv.Itoa(c.Writer.Status()) method := c.Request.Method httpRequestsTotal.WithLabelValues(method, path, status).Inc() httpRequestDuration.WithLabelValues(method, path, status).Observe(duration) } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestMetricsMiddleware(t *testing.T) { router := gin.New() router.Use(Metrics()) router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) // VĂ©rifier que les mĂ©triques ont Ă©tĂ© enregistrĂ©es // (nĂ©cessite accĂšs au registry Prometheus) } ``` ### Definition of Done - [x] MĂ©triques Prometheus créées (veza_http_requests_total, veza_http_request_duration_seconds) - [x] Middleware Metrics() implĂ©mentĂ© avec mesure de durĂ©e - [x] Labels: method, path, status - [x] Gestion path vide (utilise Request.URL.Path si FullPath vide) - [x] IntĂ©grĂ© dans SetupMiddleware (aprĂšs RequestID) - [x] Tests unitaires créés (8 tests, coverage > 85%) - [x] Tests pour diffĂ©rents codes status, mĂ©thodes HTTP, et durĂ©es - [x] MĂ©triques visibles dans /metrics - [x] Code review approuvĂ© --- ## T0023: Add Database Metrics Collection ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-018 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0010 ✅, T0021 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter collecte de mĂ©triques de base de donnĂ©es (query duration, connection pool stats) via Prometheus. ### Fichiers Ă  Modifier - `veza-backend-api/internal/database/pool.go` - `veza-backend-api/internal/metrics/prometheus.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er mĂ©triques Prometheus (db_queries_total, db_query_duration_seconds, db_connections) **Étape 2**: Wrapper pour mesurer durĂ©e queries **Étape 3**: Exposer pool stats (open, idle, in_use) **Étape 4**: IntĂ©grer dans pool.go **Étape 5**: Tests de mĂ©triques ### Code Snippets **veza-backend-api/internal/metrics/prometheus.go** (additions): ```go var ( dbQueriesTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "veza_db_queries_total", Help: "Total number of database queries", }, []string{"operation", "table"}, ) dbQueryDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "veza_db_query_duration_seconds", Help: "Database query duration in seconds", Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5}, }, []string{"operation", "table"}, ) dbConnections = promauto.NewGaugeVec( prometheus.GaugeOpts{ Name: "veza_db_connections", Help: "Number of database connections", }, []string{"state"}, // open, idle, in_use ) ) // RecordDBQuery enregistre une requĂȘte DB func RecordDBQuery(operation, table string, duration time.Duration) { dbQueriesTotal.WithLabelValues(operation, table).Inc() dbQueryDuration.WithLabelValues(operation, table).Observe(duration.Seconds()) } // UpdateDBConnections met Ă  jour les mĂ©triques de connexions func UpdateDBConnections(open, idle, inUse int) { dbConnections.WithLabelValues("open").Set(float64(open)) dbConnections.WithLabelValues("idle").Set(float64(idle)) dbConnections.WithLabelValues("in_use").Set(float64(inUse)) } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestDBMetrics(t *testing.T) { start := time.Now() time.Sleep(10 * time.Millisecond) duration := time.Since(start) metrics.RecordDBQuery("SELECT", "users", duration) // VĂ©rifier mĂ©triques } ``` ### Definition of Done - [x] MĂ©triques DB créées (veza_db_queries_total, veza_db_query_duration_seconds, veza_db_connections) - [x] Fonction RecordDBQuery() pour enregistrer les requĂȘtes - [x] Fonction UpdateDBConnections() pour les stats du pool - [x] Fonction MeasureQuery() helper pour wrapper les opĂ©rations DB - [x] Pool stats exposĂ©s (open, idle, in_use) via GetPoolStats() - [x] IntĂ©grĂ© dans pool.go (GetPoolStats met Ă  jour les mĂ©triques) - [x] Tests unitaires créés (8 tests, coverage > 85%) - [x] MĂ©triques visibles dans /metrics - [x] Code review approuvĂ© --- ## T0024: Implement Log Rotation Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-019 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0008 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer rotation automatique des logs avec taille max, retention, et compression pour Ă©viter saturation disque. ### Fichiers Ă  Modifier - `veza-backend-api/internal/logging/logger.go` ### ImplĂ©mentation **Étape 1**: Ajouter dĂ©pendance lumberjack ou file-rotatelogs **Étape 2**: Configurer rotation par taille (100MB) et temps (daily) **Étape 3**: Configurer retention (30 jours) **Étape 4**: Activer compression **Étape 5**: Tests de rotation ### Code Snippets **veza-backend-api/internal/logging/logger.go** (modifications): ```go import ( "gopkg.in/natefinch/lumberjack.v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) func NewLoggerWithRotation(env, logFile string) (*Logger, error) { var config zap.Config if env == "production" { config = zap.NewProductionConfig() } else { config = zap.NewDevelopmentConfig() } // Rotation des logs writer := &lumberjack.Logger{ Filename: logFile, MaxSize: 100, // MB MaxBackups: 10, MaxAge: 30, // days Compress: true, } core := zapcore.NewCore( config.EncoderConfig, zapcore.AddSync(writer), config.Level, ) logger := zap.New(core) return &Logger{zap: logger}, nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestLogRotation(t *testing.T) { logger, err := NewLoggerWithRotation("production", "/tmp/test.log") require.NoError(t, err) // Écrire beaucoup de logs for i := 0; i < 10000; i++ { logger.Info("test log", zap.Int("iteration", i)) } // VĂ©rifier que les fichiers de rotation existent } ``` ### Definition of Done - [x] DĂ©pendance lumberjack.v2 ajoutĂ©e - [x] Fonction NewLoggerWithRotation() créée - [x] Rotation configurĂ©e (100MB max par fichier) - [x] Retention configurĂ©e (30 jours, 10 backups max) - [x] Compression activĂ©e (gzip pour les vieux logs) - [x] Support production et development - [x] Tests unitaires créés (7 tests, coverage > 85%) - [x] Tests pour Ă©critures concurrentes - [x] Pas de perte de logs lors rotation (vĂ©rifiĂ© avec Sync) - [x] Code review approuvĂ© --- ## T0025: Add Request Tracing Middleware ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-020 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0011 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter tracing distribuĂ© avec propagation de trace ID entre services pour debugging end-to-end. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/tracing.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/request_id.go` ### ImplĂ©mentation **Étape 1**: GĂ©nĂ©rer trace ID (format W3C Trace Context) **Étape 2**: Propagate trace ID via headers **Étape 3**: Logger trace ID avec chaque log **Étape 4**: Support span ID (optionnel) **Étape 5**: Tests de propagation ### Code Snippets **veza-backend-api/internal/middleware/tracing.go**: ```go package middleware import ( "github.com/gin-gonic/gin" "github.com/google/uuid" ) const ( TraceIDHeader = "X-Trace-ID" TraceIDKey = "trace_id" ) // Tracing middleware pour gĂ©nĂ©rer et propager trace ID func Tracing() gin.HandlerFunc { return func(c *gin.Context) { traceID := c.GetHeader(TraceIDHeader) if traceID == "" { traceID = uuid.New().String() } c.Set(TraceIDKey, traceID) c.Header(TraceIDHeader, traceID) c.Next() } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestTracing(t *testing.T) { router := gin.New() router.Use(Tracing()) router.GET("/test", func(c *gin.Context) { traceID, _ := c.Get("trace_id") c.JSON(200, gin.H{"trace_id": traceID}) }) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/test", nil) router.ServeHTTP(w, req) assert.NotEmpty(t, w.Header().Get("X-Trace-ID")) } ``` ### Definition of Done - [x] Middleware Tracing() créé - [x] Trace ID gĂ©nĂ©rĂ© (UUID v4) si non prĂ©sent - [x] Header X-Trace-ID propagĂ© (rĂ©utilisĂ© si prĂ©sent dans requĂȘte) - [x] Span ID support (UUID v4, optionnel) - [x] Header X-Span-ID propagĂ© - [x] Trace ID et Span ID dans logs (intĂ©grĂ© dans RequestLogger) - [x] Fonctions helper GetTraceID() et GetSpanID() - [x] Tests unitaires créés (10 tests, coverage > 90%) - [x] Tests de propagation et unicitĂ© - [x] Compatible W3C Trace Context (format UUID compatible) - [x] Code review approuvĂ© --- ## T0026: Create System Metrics Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-021 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0012 ✅, T0023 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint `/system/metrics` retournant mĂ©triques systĂšme (CPU, mĂ©moire, goroutines) en JSON pour monitoring. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/system_metrics.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/routes/routes.go` ### ImplĂ©mentation **Étape 1**: Utiliser runtime.ReadMemStats() **Étape 2**: Collecter stats: CPU, mĂ©moire, goroutines **Étape 3**: Retourner JSON avec mĂ©triques **Étape 4**: Route GET /system/metrics **Étape 5**: Tests de collecte ### Code Snippets **veza-backend-api/internal/handlers/system_metrics.go**: ```go package handlers import ( "runtime" "time" "github.com/gin-gonic/gin" ) // SystemMetrics retourne les mĂ©triques systĂšme func SystemMetrics(c *gin.Context) { var m runtime.MemStats runtime.ReadMemStats(&m) metrics := gin.H{ "timestamp": time.Now().Unix(), "memory": gin.H{ "alloc_mb": bToMb(m.Alloc), "total_alloc_mb": bToMb(m.TotalAlloc), "sys_mb": bToMb(m.Sys), "num_gc": m.NumGC, }, "goroutines": runtime.NumGoroutine(), "cpu_count": runtime.NumCPU(), } c.JSON(200, metrics) } func bToMb(b uint64) uint64 { return b / 1024 / 1024 } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestSystemMetrics(t *testing.T) { router := gin.New() router.GET("/system/metrics", handlers.SystemMetrics) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/system/metrics", nil) router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), "memory") assert.Contains(t, w.Body.String(), "goroutines") } ``` ### Definition of Done - [x] Handler SystemMetrics() créé - [x] Endpoint /system/metrics créé (route GET /api/v1/system/metrics) - [x] MĂ©triques mĂ©moire collectĂ©es (alloc_mb, total_alloc_mb, sys_mb, num_gc) - [x] Nombre de goroutines exposĂ© - [x] Nombre de CPUs exposĂ© - [x] Timestamp Unix inclus - [x] Tests unitaires créés (8 tests, coverage > 90%) - [x] Format JSON valide - [x] Fonction helper bToMb() pour conversion - [x] Code review approuvĂ© --- ## T0027: Implement Log Level Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-022 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0008 ✅, T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Permettre configuration du niveau de log (DEBUG, INFO, WARN, ERROR) via variable d'environnement. ### Fichiers Ă  Modifier - `veza-backend-api/internal/logging/logger.go` - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: Ajouter LOG_LEVEL dans config **Étape 2**: Parser niveau de log depuis env **Étape 3**: Configurer zap avec niveau dynamique **Étape 4**: Valeur par dĂ©faut: INFO **Étape 5**: Tests de niveaux ### Code Snippets **veza-backend-api/internal/logging/logger.go** (modifications): ```go func NewLogger(env, logLevel string) (*Logger, error) { var config zap.Config if env == "production" { config = zap.NewProductionConfig() } else { config = zap.NewDevelopmentConfig() } // Configurer le niveau de log level, err := zapcore.ParseLevel(logLevel) if err != nil { level = zapcore.InfoLevel // default } config.Level = zap.NewAtomicLevelAt(level) logger, err := config.Build() if err != nil { return nil, err } return &Logger{zap: logger}, nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestLogLevelConfiguration(t *testing.T) { logger, err := NewLogger("development", "debug") require.NoError(t, err) // VĂ©rifier que le niveau est correct logger.Debug("debug message") // Should log logger.Info("info message") // Should log } ``` ### Definition of Done - [x] LOG_LEVEL ajoutĂ© dans config.go (variable d'environnement) - [x] NewLogger() modifiĂ© pour accepter logLevel paramĂštre - [x] NewLoggerWithRotation() modifiĂ© pour accepter logLevel paramĂštre - [x] Parser niveau avec zapcore.ParseLevel() - [x] Niveaux supportĂ©s: DEBUG, INFO, WARN, ERROR - [x] Valeur par dĂ©faut: INFO (si vide ou invalide) - [x] Tests unitaires créés (12 tests, coverage > 90%) - [x] Tests pour tous les niveaux et cas limites - [x] Tests mis Ă  jour pour nouvelles signatures - [x] Niveau changeable via env var LOG_LEVEL - [x] Code review approuvĂ© --- ## T0028: Add Structured Error Logging ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-023 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0002 ✅, T0008 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique AmĂ©liorer logging des erreurs avec stack trace, contexte utilisateur, et format structurĂ© pour debugging. ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/error_handler.go` ### ImplĂ©mentation **Étape 1**: Logger stack trace pour erreurs internes **Étape 2**: Inclure contexte (request_id, user_id) **Étape 3**: Format JSON structurĂ© **Étape 4**: Niveau ERROR pour AppError **Étape 5**: Tests de format ### Code Snippets **veza-backend-api/internal/middleware/error_handler.go** (modifications): ```go import "runtime/debug" // Dans ErrorHandler, amĂ©liorer le logging: logger.Error("Application error", zap.Int("code", int(appErr.Code)), zap.String("message", appErr.Message), zap.Int("http_status", httpStatus), zap.String("request_id", requestID), zap.ByteString("stack_trace", debug.Stack()), // Pour erreurs internes ) ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestStructuredErrorLogging(t *testing.T) { // VĂ©rifier que les logs contiennent tous les champs requis } ``` ### Definition of Done - [x] Stack trace loggĂ© pour erreurs internes (via debug.Stack()) - [x] Contexte complet inclus (request_id, user_id, trace_id, span_id) - [x] Format JSON structurĂ© avec zap - [x] Niveau ERROR pour AppError et erreurs internes - [x] DĂ©tails de validation inclus dans logs - [x] Erreur causale (Err) incluse si prĂ©sente - [x] Tests unitaires créés (7 tests, coverage > 85%) - [x] Validation format JSON valide - [x] VĂ©rification absence de donnĂ©es sensibles - [x] Code review approuvĂ© --- ## T0029: Create Metrics Aggregation Service ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-024 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0020 ✅, T0021 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service pour agrĂ©ger mĂ©triques sur fenĂȘtres de temps (1min, 5min, 1h) pour analytics. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/metrics/aggregation.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/metrics/errors.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct AggregatedMetrics avec fenĂȘtres **Étape 2**: AgrĂ©ger par fenĂȘtre (sliding window) **Étape 3**: Exposer endpoint /metrics/aggregated **Étape 4**: Nettoyer anciennes fenĂȘtres **Étape 5**: Tests d'agrĂ©gation ### Code Snippets **veza-backend-api/internal/metrics/aggregation.go**: ```go package metrics import ( "sync" "time" ) type TimeWindow struct { Start time.Time End time.Time Errors int64 Requests int64 } type AggregatedMetrics struct { mu sync.RWMutex windows map[string][]TimeWindow // key: "1m", "5m", "1h" } func NewAggregatedMetrics() *AggregatedMetrics { return &AggregatedMetrics{ windows: make(map[string][]TimeWindow), } } func (a *AggregatedMetrics) AddError(window string) { a.mu.Lock() defer a.mu.Unlock() // ImplĂ©menter agrĂ©gation } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestMetricsAggregation(t *testing.T) { agg := NewAggregatedMetrics() agg.AddError("1m") // VĂ©rifier agrĂ©gation } ``` ### Definition of Done - [x] AgrĂ©gation par fenĂȘtres (1m, 5m, 1h) implĂ©mentĂ©e - [x] Sliding window avec fenĂȘtres temporelles tronquĂ©es - [x] Endpoint /metrics/aggregated créé (GET /api/v1/metrics/aggregated) - [x] Support query parameter ?window=1m|5m|1h - [x] IntĂ©gration avec ErrorMetrics existant - [x] AgrĂ©gation des erreurs par code et status HTTP - [x] Support agrĂ©gation des requĂȘtes - [x] Nettoyage automatique anciennes fenĂȘtres (routine background) - [x] Tests unitaires créés (10 tests pour aggregation, 6 tests pour handler, coverage > 85%) - [x] Tests d'intĂ©gration avec ErrorMetrics - [x] Code review approuvĂ© --- ## T0030: Optimize Log Performance ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-025 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0008 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Optimiser performance du logging avec buffering, async writes, et sampling pour haute charge. ### Fichiers Ă  Modifier - `veza-backend-api/internal/logging/logger.go` ### ImplĂ©mentation **Étape 1**: Activer buffering zap **Étape 2**: Async writes avec goroutines **Étape 3**: Sampling pour Ă©viter spam **Étape 4**: Benchmark performance **Étape 5**: Tests de charge ### Code Snippets **veza-backend-api/internal/logging/logger.go** (modifications): ```go import "go.uber.org/zap/zapcore" func NewOptimizedLogger(env string) (*Logger, error) { config := zap.NewProductionConfig() // Sampling pour Ă©viter spam config.Sampling = &zap.SamplingConfig{ Initial: 100, Thereafter: 100, } logger, err := config.Build( zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel), ) return &Logger{zap: logger}, err } ``` ### Tests Ă  Écrire **Performance Tests**: ```go func BenchmarkLogging(b *testing.B) { logger, _ := NewOptimizedLogger("production") b.ResetTimer() for i := 0; i < b.N; i++ { logger.Info("test message") } } ``` ### Definition of Done - [x] Buffering activĂ© (256KB buffer pour rĂ©duire appels systĂšme) - [x] Async writes configurĂ©s (goroutine avec channel buffered) - [x] Sampling activĂ© (Initial: 100, Thereafter: 100) - [x] Flush pĂ©riodique (100ms) pour garantir Ă©criture - [x] NewOptimizedLogger() créée - [x] NewOptimizedLoggerWithRotation() créée - [x] Benchmark performance créés (comparaison standard vs optimisĂ©) - [x] Tests de performance (< 1ms/log) - [x] Tests de charge (10K logs/sec) - [x] Tests concurrents (10 goroutines) - [x] Tests avec rotation - [x] Code review approuvĂ© --- ## T0031: Add Configuration Validation ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-026 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter validation des valeurs de configuration au dĂ©marrage de l'application pour dĂ©tecter les erreurs de configuration avant que l'application ne dĂ©marre. ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction Validate() pour Config **Étape 2**: Valider port (1-65535) **Étape 3**: Valider URLs (database, redis) **Étape 4**: Valider JWT secret (min length) **Étape 5**: Retourner erreur structurĂ©e si invalide ### Code Snippets **veza-backend-api/internal/config/config.go** (modifications): ```go import "errors" // Validate valide la configuration func (c *Config) Validate() error { if c.AppPort < 1 || c.AppPort > 65535 { return errors.New("APP_PORT must be between 1 and 65535") } if c.JWTSecret == "" || len(c.JWTSecret) < 32 { return errors.New("JWT_SECRET must be at least 32 characters") } if c.DatabaseURL == "" { return errors.New("DATABASE_URL is required") } if c.RedisURL == "" { return errors.New("REDIS_URL is required") } return nil } // Dans NewConfig(), ajouter validation: func NewConfig() (*Config, error) { // ... configuration ... // Valider la configuration if err := config.Validate(); err != nil { return nil, fmt.Errorf("invalid configuration: %w", err) } return config, nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestConfig_Validate(t *testing.T) { tests := []struct { name string config *Config wantErr bool }{ { name: "valid config", config: &Config{ AppPort: 8080, JWTSecret: strings.Repeat("a", 32), DatabaseURL: "postgres://...", RedisURL: "redis://...", }, wantErr: false, }, { name: "invalid port", config: &Config{ AppPort: 99999, }, wantErr: true, }, { name: "JWT secret too short", config: &Config{ JWTSecret: "short", }, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := tt.config.Validate() if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) } }) } } ``` ### Definition of Done - [x] Fonction Validate() créée - [x] Validation port (1-65535) avec limites incluses - [x] Validation JWT secret (min 32 chars) - [x] Validation URLs requises (DatabaseURL, RedisURL) - [x] Validation format URLs (postgres/postgresql/sqlite pour DB, redis/rediss pour Redis) - [x] Tests unitaires créés (14 tests, coverage > 85%) - [x] Validation appelĂ©e dans NewConfig() avec logging d'erreur - [x] AppPort ajoutĂ© Ă  Config struct - [x] Erreurs claires et structurĂ©es - [x] Code review approuvĂ© --- ## T0032: Add Environment-Specific Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-027 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er support pour fichiers de configuration spĂ©cifiques par environnement (.env.development, .env.production, .env.test). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/env_loader.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction LoadEnvFile(env string) **Étape 2**: Charger .env.{environment} si existe **Étape 3**: Charger .env en fallback **Étape 4**: Prioriser variables d'environnement systĂšme **Étape 5**: Tests avec diffĂ©rents environnements ### Code Snippets **veza-backend-api/internal/config/env_loader.go**: ```go package config import ( "os" "github.com/joho/godotenv" ) // LoadEnvFiles charge les fichiers .env selon l'environnement // Charge dans l'ordre: .env.{env}, .env // Les variables d'environnement systĂšme ont prioritĂ© func LoadEnvFiles(env string) error { // Charger .env.{env} si existe envFile := ".env." + env if _, err := os.Stat(envFile); err == nil { if err := godotenv.Load(envFile); err != nil { return fmt.Errorf("failed to load %s: %w", envFile, err) } } // Charger .env en fallback (ignore si n'existe pas) _ = godotenv.Load() return nil } ``` **veza-backend-api/internal/config/config.go** (modifications): ```go func Load() (*EnvConfig, error) { env := getEnv("APP_ENV", "development") // Charger les fichiers .env selon l'environnement if err := LoadEnvFiles(env); err != nil { return nil, err } // ... reste du code ... } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestLoadEnvFiles(t *testing.T) { tests := []struct { name string env string wantErr bool }{ {"development", "development", false}, {"production", "production", false}, {"test", "test", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := LoadEnvFiles(tt.env) if (err != nil) != tt.wantErr { t.Errorf("LoadEnvFiles() error = %v, wantErr %v", err, tt.wantErr) } }) } } ``` ### Definition of Done - [x] LoadEnvFiles() créée (internal/config/env_loader.go) - [x] Support .env.{environment} (development, production, test, etc.) - [x] Fallback sur .env si fichier spĂ©cifique n'existe pas - [x] PrioritĂ© variables systĂšme (godotenv ne surcharge pas) - [x] IntĂ©grĂ© dans Load() et NewConfig() - [x] Tests unitaires créés (5 tests, coverage > 85%) - [x] Tests pour priority, chargement multiple fichiers, fichiers inexistants - [x] Gestion erreurs appropriĂ©e - [x] Code review approuvĂ© --- ## T0033: Add Configuration Documentation Generator ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-028 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er gĂ©nĂ©rateur de documentation pour toutes les variables d'environnement avec descriptions, types, valeurs par dĂ©faut. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/docs.go` - `veza-backend-api/docs/CONFIGURATION.md` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct EnvVarDoc avec mĂ©tadonnĂ©es **Étape 2**: Documenter toutes variables dans map **Étape 3**: GĂ©nĂ©rer markdown automatiquement **Étape 4**: Inclure exemples et valeurs par dĂ©faut **Étape 5**: Tests de gĂ©nĂ©ration ### Code Snippets **veza-backend-api/internal/config/docs.go**: ```go package config import ( "fmt" "os" "sort" ) type EnvVarDoc struct { Name string Type string Required bool Default string Description string Example string } var envVarsDocs = map[string]EnvVarDoc{ "APP_ENV": { Name: "APP_ENV", Type: "string", Required: false, Default: "development", Description: "Environment (development, production, test)", Example: "production", }, "APP_PORT": { Name: "APP_PORT", Type: "int", Required: false, Default: "8080", Description: "Port for HTTP server", Example: "8080", }, "JWT_SECRET": { Name: "JWT_SECRET", Type: "string", Required: true, Default: "", Description: "Secret key for JWT signing (min 32 chars)", Example: "your-super-secret-jwt-key-here", }, // ... autres variables ... } // GenerateConfigDocs gĂ©nĂšre la documentation markdown func GenerateConfigDocs() string { var keys []string for k := range envVarsDocs { keys = append(keys, k) } sort.Strings(keys) md := "# Configuration Variables\n\n" md += "This document lists all environment variables used by the application.\n\n" for _, key := range keys { doc := envVarsDocs[key] md += fmt.Sprintf("## %s\n\n", doc.Name) md += fmt.Sprintf("**Type**: `%s`\n\n", doc.Type) md += fmt.Sprintf("**Required**: %v\n\n", doc.Required) if doc.Default != "" { md += fmt.Sprintf("**Default**: `%s`\n\n", doc.Default) } md += fmt.Sprintf("**Description**: %s\n\n", doc.Description) if doc.Example != "" { md += fmt.Sprintf("**Example**: `%s`\n\n", doc.Example) } md += "---\n\n" } return md } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestGenerateConfigDocs(t *testing.T) { docs := GenerateConfigDocs() assert.Contains(t, docs, "# Configuration Variables") assert.Contains(t, docs, "APP_ENV") assert.Contains(t, docs, "JWT_SECRET") } ``` ### Definition of Done - [x] EnvVarDoc struct créée (internal/config/docs.go) - [x] Toutes variables documentĂ©es (14 variables: APP_ENV, APP_PORT, JWT_SECRET, DATABASE_URL, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME, REDIS_URL, CORS_ALLOWED_ORIGINS, RATE_LIMIT_LIMIT, RATE_LIMIT_WINDOW, LOG_LEVEL) - [x] GenerateConfigDocs() créée avec format markdown structurĂ© - [x] GetAllEnvVarDocs() créée pour introspection - [x] Documentation markdown gĂ©nĂ©rĂ©e avec sections, types, required, defaults, examples - [x] Tests unitaires créés (7 tests, coverage > 90%) - [x] Tests pour structure, contenu, exemples, valeurs par dĂ©faut - [x] Script de gĂ©nĂ©ration CONFIGURATION.md disponible - [x] Code review approuvĂ© --- ## T0034: Add Configuration Hot Reload Support ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-029 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0009 ✅, T0031 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter support pour rechargement Ă  chaud de certaines configurations sans redĂ©marrer l'application (log level, rate limits). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/reloader.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er interface Reloadable **Étape 2**: ImplĂ©menter reload pour log level **Étape 3**: ImplĂ©menter reload pour rate limits **Étape 4**: Ajouter endpoint /admin/config/reload **Étape 5**: Tests de reload ### Code Snippets **veza-backend-api/internal/config/reloader.go**: ```go package config import ( "sync" "go.uber.org/zap" ) // Reloadable reprĂ©sente une configuration qui peut ĂȘtre rechargĂ©e type Reloadable interface { Reload() error } // ConfigReloader gĂšre le rechargement de configurations type ConfigReloader struct { mu sync.RWMutex config *Config logger *zap.Logger } func NewConfigReloader(config *Config, logger *zap.Logger) *ConfigReloader { return &ConfigReloader{ config: config, logger: logger, } } // ReloadLogLevel recharge le niveau de log func (r *ConfigReloader) ReloadLogLevel() error { r.mu.Lock() defer r.mu.Unlock() newLevel := getEnv("LOG_LEVEL", "INFO") // ImplĂ©menter changement de niveau de log r.logger.Info("Log level reloaded", zap.String("level", newLevel)) return nil } // ReloadRateLimits recharge les limites de rate limiting func (r *ConfigReloader) ReloadRateLimits() error { r.mu.Lock() defer r.mu.Unlock() // ImplĂ©menter rechargement des limites r.logger.Info("Rate limits reloaded") return nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestConfigReloader_ReloadLogLevel(t *testing.T) { config := &Config{LogLevel: "INFO"} logger := zap.NewNop() reloader := NewConfigReloader(config, logger) err := reloader.ReloadLogLevel() assert.NoError(t, err) } ``` ### Definition of Done - [x] ConfigReloader créé (internal/config/reloader.go) - [x] Interface Reloadable dĂ©finie - [x] Reload log level implĂ©mentĂ© (depuis LOG_LEVEL env var) - [x] Reload rate limits implĂ©mentĂ© (depuis RATE_LIMIT_LIMIT et RATE_LIMIT_WINDOW) - [x] UpdateLimits() ajoutĂ© Ă  SimpleRateLimiter - [x] SetLevel() et GetLevel() ajoutĂ©s Ă  Logger (base pour future implĂ©mentation complĂšte) - [x] Endpoint POST /admin/config/reload créé (supporte type: all, log_level, rate_limits) - [x] Endpoint GET /admin/config créé (rĂ©cupĂšre config actuelle) - [x] Tests unitaires créés (5 tests, coverage > 80%) - [x] Thread-safe avec mutex (sync.RWMutex) - [x] IntĂ©gration dans routes admin avec authentification - [x] Code review approuvĂ© --- ## T0035: Add Configuration Testing Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-030 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0009 ✅, T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er utilitaires de test pour faciliter la crĂ©ation de configurations de test dans les tests unitaires et d'intĂ©gration. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/testutils.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er NewTestConfig() helper **Étape 2**: CrĂ©er WithEnv() pour override **Étape 3**: CrĂ©er ResetEnv() pour cleanup **Étape 4**: Ajouter exemples d'utilisation **Étape 5**: Tests des utilitaires ### Code Snippets **veza-backend-api/internal/config/testutils.go**: ```go package config import ( "os" "testing" ) // NewTestConfig crĂ©e une configuration de test avec valeurs par dĂ©faut func NewTestConfig(t *testing.T) *Config { return &Config{ AppPort: 8080, AppEnv: "test", JWTSecret: "test-jwt-secret-key-minimum-32-characters", DatabaseURL: "postgres://test:test@localhost:5432/test_db", RedisURL: "redis://localhost:6379/0", CORSOrigins: []string{"*"}, RateLimitLimit: 100, RateLimitWindow: 60, LogLevel: "DEBUG", } } // WithEnv dĂ©finit temporairement une variable d'environnement pour les tests func WithEnv(key, value string) func() { oldValue := os.Getenv(key) os.Setenv(key, value) return func() { if oldValue == "" { os.Unsetenv(key) } else { os.Setenv(key, oldValue) } } } // ResetEnv rĂ©initialise toutes les variables d'environnement de test func ResetEnv() { testVars := []string{ "APP_ENV", "APP_PORT", "JWT_SECRET", "DATABASE_URL", "REDIS_URL", "LOG_LEVEL", } for _, v := range testVars { os.Unsetenv(v) } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestNewTestConfig(t *testing.T) { config := NewTestConfig(t) assert.Equal(t, "test", config.AppEnv) assert.Equal(t, 8080, config.AppPort) assert.NotEmpty(t, config.JWTSecret) } func TestWithEnv(t *testing.T) { reset := WithEnv("TEST_VAR", "test_value") defer reset() assert.Equal(t, "test_value", os.Getenv("TEST_VAR")) reset() assert.Empty(t, os.Getenv("TEST_VAR")) } ``` ### Definition of Done - [x] NewTestConfig() créé (internal/config/testutils.go) - [x] WithEnv() helper créé avec fonction de cleanup - [x] ResetEnv() créé pour nettoyer toutes les variables de test - [x] WithMultipleEnv() bonus ajoutĂ© pour dĂ©finir plusieurs variables Ă  la fois - [x] Tests unitaires créés (9 tests, coverage > 85%) - [x] Tests pour NewTestConfig, WithEnv, ResetEnv, WithMultipleEnv - [x] Tests pour isolation entre instances et restauration de valeurs - [x] Documentation avec exemples dans les commentaires - [x] Logger de test intĂ©grĂ© (zaptest.NewLogger) - [x] Code review approuvĂ© --- ## T0036: Add Configuration Schema Validation ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-031 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0031 ✅, T0033 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter validation de schĂ©ma pour les valeurs de configuration avec types stricts (port range, URL format, enum values) et messages d'erreur clairs. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/validator.go` - `veza-backend-api/internal/config/validator_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct ConfigValidator **Étape 2**: ImplĂ©menter validatePort(port int) error **Étape 3**: ImplĂ©menter validateURL(url, scheme string) error **Étape 4**: ImplĂ©menter validateEnum(value string, allowed []string) error **Étape 5**: IntĂ©grer dans Config.Validate() ### Code Snippets **veza-backend-api/internal/config/validator.go**: ```go package config import ( "fmt" "net/url" "strings" ) // ConfigValidator valide la configuration selon des rĂšgles strictes (T0036) type ConfigValidator struct{} // NewConfigValidator crĂ©e un nouveau validateur func NewConfigValidator() *ConfigValidator { return &ConfigValidator{} } // ValidatePort valide qu'un port est dans la plage valide (1-65535) func (v *ConfigValidator) ValidatePort(port int) error { if port < 1 || port > 65535 { return fmt.Errorf("port must be between 1 and 65535, got %d", port) } return nil } // ValidateURL valide qu'une URL a le schĂ©ma attendu func (v *ConfigValidator) ValidateURL(urlStr, expectedScheme string) error { if urlStr == "" { return fmt.Errorf("URL cannot be empty") } parsedURL, err := url.Parse(urlStr) if err != nil { return fmt.Errorf("invalid URL format: %w", err) } if parsedURL.Scheme != expectedScheme { return fmt.Errorf("URL must have scheme %s, got %s", expectedScheme, parsedURL.Scheme) } return nil } // ValidateEnum valide qu'une valeur fait partie des valeurs autorisĂ©es func (v *ConfigValidator) ValidateEnum(value string, allowed []string) error { for _, allowedValue := range allowed { if value == allowedValue { return nil } } return fmt.Errorf("value '%s' is not allowed. Allowed values: %s", value, strings.Join(allowed, ", ")) } // ValidateSecretLength valide qu'un secret a une longueur minimale func (v *ConfigValidator) ValidateSecretLength(secret string, minLength int) error { if len(secret) < minLength { return fmt.Errorf("secret must be at least %d characters, got %d", minLength, len(secret)) } return nil } // ValidatePositiveInt valide qu'un entier est positif func (v *ConfigValidator) ValidatePositiveInt(value int, fieldName string) error { if value <= 0 { return fmt.Errorf("%s must be positive, got %d", fieldName, value) } return nil } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestConfigValidator_ValidatePort(t *testing.T) { validator := NewConfigValidator() tests := []struct { name string port int wantErr bool }{ {"valid port", 8080, false}, {"min port", 1, false}, {"max port", 65535, false}, {"invalid negative", -1, true}, {"invalid too high", 65536, true}, {"invalid zero", 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validator.ValidatePort(tt.port) if (err != nil) != tt.wantErr { t.Errorf("ValidatePort() error = %v, wantErr %v", err, tt.wantErr) } }) } } func TestConfigValidator_ValidateURL(t *testing.T) { validator := NewConfigValidator() tests := []struct { name string url string expectedScheme string wantErr bool }{ {"valid postgres URL", "postgres://user:pass@localhost:5432/db", "postgres", false}, {"valid redis URL", "redis://localhost:6379", "redis", false}, {"invalid scheme", "http://localhost", "postgres", true}, {"empty URL", "", "postgres", true}, {"malformed URL", "://invalid", "postgres", true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validator.ValidateURL(tt.url, tt.expectedScheme) if (err != nil) != tt.wantErr { t.Errorf("ValidateURL() error = %v, wantErr %v", err, tt.wantErr) } }) } } ``` ### Definition of Done - [x] ConfigValidator créé avec mĂ©thodes de validation (internal/config/validator.go) - [x] ValidatePort() implĂ©mentĂ© (1-65535) avec tests complets - [x] ValidateURL() implĂ©mentĂ© avec vĂ©rification de schĂ©ma (support multiple schemes) - [x] ValidateEnum() implĂ©mentĂ© pour valeurs autorisĂ©es (case-sensitive) - [x] ValidateSecretLength() et ValidatePositiveInt() implĂ©mentĂ©s - [x] IntĂ©grĂ© dans Config.Validate() avec messages d'erreur clairs (wrapped errors) - [x] Validation de LogLevel, RateLimitLimit, RateLimitWindow ajoutĂ©e - [x] Tests unitaires créés (11 tests, coverage > 90%) - [x] Tests pour tous les cas limites et messages d'erreur - [x] Code review approuvĂ© --- ## T0037: Add Configuration Secrets Management ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-032 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0009 ✅, T0031 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter support pour gestion sĂ©curisĂ©e des secrets avec support de secrets managers (AWS Secrets Manager, HashiCorp Vault) et masquage dans les logs. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/secrets.go` - `veza-backend-api/internal/config/secrets_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er interface SecretsProvider **Étape 2**: ImplĂ©menter EnvSecretsProvider (variables d'environnement) **Étape 3**: ImplĂ©menter masquage des secrets dans logs **Étape 4**: Ajouter mĂ©thode GetSecret(name) string **Étape 5**: IntĂ©grer dans NewConfig() ### Code Snippets **veza-backend-api/internal/config/secrets.go**: ```go package config import ( "fmt" "os" "strings" ) // SecretsProvider dĂ©finit l'interface pour les fournisseurs de secrets (T0037) type SecretsProvider interface { GetSecret(name string) (string, error) IsSecret(name string) bool } // EnvSecretsProvider rĂ©cupĂšre les secrets depuis les variables d'environnement type EnvSecretsProvider struct { secretKeys map[string]bool } // NewEnvSecretsProvider crĂ©e un nouveau fournisseur de secrets depuis l'environnement func NewEnvSecretsProvider(secretKeys []string) *EnvSecretsProvider { keysMap := make(map[string]bool) for _, key := range secretKeys { keysMap[key] = true } return &EnvSecretsProvider{secretKeys: keysMap} } // GetSecret rĂ©cupĂšre un secret depuis les variables d'environnement func (p *EnvSecretsProvider) GetSecret(name string) (string, error) { value := os.Getenv(name) if value == "" { return "", fmt.Errorf("secret %s not found", name) } return value, nil } // IsSecret vĂ©rifie si une clĂ© est un secret func (p *EnvSecretsProvider) IsSecret(name string) bool { return p.secretKeys[name] } // MaskSecret masque un secret pour l'affichage dans les logs (T0037) func MaskSecret(secret string) string { if secret == "" { return "" } if len(secret) <= 8 { return "****" } return secret[:4] + "****" + secret[len(secret)-4:] } // MaskConfigValue masque une valeur si c'est un secret func MaskConfigValue(key, value string, provider SecretsProvider) string { if provider != nil && provider.IsSecret(key) { return MaskSecret(value) } return value } // DefaultSecretKeys retourne la liste des clĂ©s considĂ©rĂ©es comme secrets func DefaultSecretKeys() []string { return []string{ "JWT_SECRET", "DB_PASSWORD", "REDIS_PASSWORD", "AWS_SECRET_ACCESS_KEY", "STRIPE_SECRET_KEY", } } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestEnvSecretsProvider_GetSecret(t *testing.T) { os.Setenv("TEST_SECRET", "my-secret-value") defer os.Unsetenv("TEST_SECRET") provider := NewEnvSecretsProvider([]string{"TEST_SECRET"}) secret, err := provider.GetSecret("TEST_SECRET") require.NoError(t, err) assert.Equal(t, "my-secret-value", secret) _, err = provider.GetSecret("NONEXISTENT") assert.Error(t, err) } func TestMaskSecret(t *testing.T) { tests := []struct { name string secret string expected string }{ {"long secret", "my-super-secret-key-12345", "my-s****t-12345"}, {"short secret", "short", "****"}, {"empty secret", "", ""}, {"very short", "ab", "****"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := MaskSecret(tt.secret) assert.Equal(t, tt.expected, result) }) } } ``` ### Definition of Done - [x] Interface SecretsProvider dĂ©finie (GetSecret, IsSecret) - [x] EnvSecretsProvider implĂ©mentĂ© (internal/config/secrets.go) - [x] MaskSecret() pour masquer dans logs (4 premiers + 4 derniers, reste "****") - [x] MaskConfigValue() pour masquer automatiquement selon provider - [x] DefaultSecretKeys() avec 10 clĂ©s (JWT_SECRET, DB_PASSWORD, AWS_SECRET_ACCESS_KEY, etc.) - [x] IntĂ©grĂ© dans config.go (SecretsProvider dans Config struct) - [x] Initialisation automatique dans NewConfig() - [x] logConfigInitialized() avec masquage automatique des secrets - [x] Tests unitaires créés (12 tests, coverage > 90%) - [x] Tests pour tous les cas limites (empty, short, long secrets) - [x] Tests pour MaskConfigValue avec diffĂ©rents providers - [x] Code review approuvĂ© --- ## T0038: Add Configuration Defaults Builder ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-033 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0009 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er builder pattern pour dĂ©finir des valeurs par dĂ©faut de configuration avec chaĂźnage fluent pour amĂ©liorer la lisibilitĂ©. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/defaults.go` - `veza-backend-api/internal/config/defaults_test.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er struct ConfigDefaults **Étape 2**: ImplĂ©menter mĂ©thodes fluent (WithPort, WithLogLevel, etc.) **Étape 3**: ImplĂ©menter Build() *Config **Étape 4**: Ajouter mĂ©thode Merge() pour override **Étape 5**: Tests du builder ### Code Snippets **veza-backend-api/internal/config/defaults.go**: ```go package config import ( "go.uber.org/zap" ) // ConfigDefaults permet de construire une config avec des valeurs par dĂ©faut (T0038) type ConfigDefaults struct { appPort *int appEnv *string jwtSecret *string databaseURL *string redisURL *string corsOrigins []string rateLimitLimit *int rateLimitWindow *int logLevel *string logger *zap.Logger } // NewConfigDefaults crĂ©e un nouveau builder de defaults func NewConfigDefaults() *ConfigDefaults { return &ConfigDefaults{} } // WithPort dĂ©finit le port par dĂ©faut func (b *ConfigDefaults) WithPort(port int) *ConfigDefaults { b.appPort = &port return b } // WithEnv dĂ©finit l'environnement par dĂ©faut func (b *ConfigDefaults) WithEnv(env string) *ConfigDefaults { b.appEnv = &env return b } // WithJWTSecret dĂ©finit le secret JWT par dĂ©faut func (b *ConfigDefaults) WithJWTSecret(secret string) *ConfigDefaults { b.jwtSecret = &secret return b } // WithDatabaseURL dĂ©finit l'URL de la base de donnĂ©es par dĂ©faut func (b *ConfigDefaults) WithDatabaseURL(url string) *ConfigDefaults { b.databaseURL = &url return b } // WithRedisURL dĂ©finit l'URL Redis par dĂ©faut func (b *ConfigDefaults) WithRedisURL(url string) *ConfigDefaults { b.redisURL = &url return b } // WithCORSOrigins dĂ©finit les origines CORS par dĂ©faut func (b *ConfigDefaults) WithCORSOrigins(origins []string) *ConfigDefaults { b.corsOrigins = origins return b } // WithRateLimit dĂ©finit les limites de rate limiting par dĂ©faut func (b *ConfigDefaults) WithRateLimit(limit int, windowSeconds int) *ConfigDefaults { b.rateLimitLimit = &limit b.rateLimitWindow = &windowSeconds return b } // WithLogLevel dĂ©finit le niveau de log par dĂ©faut func (b *ConfigDefaults) WithLogLevel(level string) *ConfigDefaults { b.logLevel = &level return b } // Build construit une Config avec les valeurs par dĂ©faut func (b *ConfigDefaults) Build() *Config { config := &Config{} if b.appPort != nil { config.AppPort = *b.appPort } if b.appEnv != nil { // Note: AppEnv n'est pas dans Config, mais peut ĂȘtre utilisĂ© ailleurs } if b.jwtSecret != nil { config.JWTSecret = *b.jwtSecret } if b.databaseURL != nil { config.DatabaseURL = *b.databaseURL } if b.redisURL != nil { config.RedisURL = *b.redisURL } if len(b.corsOrigins) > 0 { config.CORSOrigins = b.corsOrigins } if b.rateLimitLimit != nil { config.RateLimitLimit = *b.rateLimitLimit } if b.rateLimitWindow != nil { config.RateLimitWindow = *b.rateLimitWindow } if b.logLevel != nil { config.LogLevel = *b.logLevel } if b.logger != nil { config.Logger = b.logger } return config } // Merge fusionne les valeurs par dĂ©faut avec une config existante (override) func (b *ConfigDefaults) Merge(config *Config) *Config { if b.appPort != nil { config.AppPort = *b.appPort } if b.jwtSecret != nil { config.JWTSecret = *b.jwtSecret } if b.databaseURL != nil { config.DatabaseURL = *b.databaseURL } if b.redisURL != nil { config.RedisURL = *b.redisURL } if len(b.corsOrigins) > 0 { config.CORSOrigins = b.corsOrigins } if b.rateLimitLimit != nil { config.RateLimitLimit = *b.rateLimitLimit } if b.rateLimitWindow != nil { config.RateLimitWindow = *b.rateLimitWindow } if b.logLevel != nil { config.LogLevel = *b.logLevel } if b.logger != nil { config.Logger = b.logger } return config } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestConfigDefaults_Build(t *testing.T) { defaults := NewConfigDefaults(). WithPort(9000). WithEnv("test"). WithJWTSecret("test-secret"). WithDatabaseURL("postgres://test"). WithLogLevel("DEBUG") config := defaults.Build() assert.Equal(t, 9000, config.AppPort) assert.Equal(t, "test-secret", config.JWTSecret) assert.Equal(t, "postgres://test", config.DatabaseURL) assert.Equal(t, "DEBUG", config.LogLevel) } func TestConfigDefaults_Merge(t *testing.T) { existingConfig := &Config{ AppPort: 8080, LogLevel: "INFO", } defaults := NewConfigDefaults(). WithPort(9000). WithLogLevel("DEBUG") merged := defaults.Merge(existingConfig) assert.Equal(t, 9000, merged.AppPort) // Override assert.Equal(t, "DEBUG", merged.LogLevel) // Override } ``` ### Definition of Done - [x] ConfigDefaults builder créé avec mĂ©thodes fluent (internal/config/defaults.go) - [x] WithPort, WithEnv, WithJWTSecret, WithDatabaseURL, WithRedisURL implĂ©mentĂ©s - [x] WithCORSOrigins, WithRateLimit, WithLogLevel, WithLogger implĂ©mentĂ©s - [x] Build() retourne Config complĂšte avec valeurs par dĂ©faut - [x] Merge() permet override de config existante (modifie l'instance existante) - [x] Pattern fluent supportĂ© (chaĂźnage de mĂ©thodes) - [x] Tests unitaires créés (15 tests, coverage > 90%) - [x] Tests pour Build(), Merge(), chaĂźnage fluent, cas limites - [x] Code review approuvĂ© --- ## T0039: Add Configuration Environment Detection ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-034 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0009 ✅, T0032 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique AmĂ©liorer la dĂ©tection automatique de l'environnement (development, staging, production) avec fallback intelligent et validation. ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction DetectEnvironment() string **Étape 2**: DĂ©tecter depuis APP_ENV, puis NODE_ENV, puis GO_ENV **Étape 3**: Fallback intelligent selon hostname ou flags **Étape 4**: Validation que l'environnement est valide **Étape 5**: Tests de dĂ©tection ### Code Snippets **veza-backend-api/internal/config/env_detection.go**: ```go package config import ( "os" "strings" ) const ( EnvDevelopment = "development" EnvStaging = "staging" EnvProduction = "production" EnvTest = "test" ) var validEnvironments = []string{ EnvDevelopment, EnvStaging, EnvProduction, EnvTest, } // DetectEnvironment dĂ©tecte l'environnement actuel (T0039) func DetectEnvironment() string { // 1. APP_ENV (prioritĂ©) if env := os.Getenv("APP_ENV"); env != "" { if isValidEnvironment(env) { return env } } // 2. NODE_ENV (compatibilitĂ©) if env := os.Getenv("NODE_ENV"); env != "" { if isValidEnvironment(env) { return env } } // 3. GO_ENV (compatibilitĂ© Go) if env := os.Getenv("GO_ENV"); env != "" { if isValidEnvironment(env) { return env } } // 4. Fallback: dĂ©tection par hostname (production si contient "prod") if hostname, err := os.Hostname(); err == nil { hostnameLower := strings.ToLower(hostname) if strings.Contains(hostnameLower, "prod") || strings.Contains(hostnameLower, "production") { return EnvProduction } if strings.Contains(hostnameLower, "staging") || strings.Contains(hostnameLower, "stage") { return EnvStaging } } // 5. Fallback par dĂ©faut: development return EnvDevelopment } // isValidEnvironment vĂ©rifie qu'un environnement est valide func isValidEnvironment(env string) bool { envLower := strings.ToLower(env) for _, validEnv := range validEnvironments { if envLower == validEnv { return true } } return false } // NormalizeEnvironment normalise le nom d'environnement (T0039) func NormalizeEnvironment(env string) string { envLower := strings.ToLower(env) // Mappings courants mappings := map[string]string{ "dev": EnvDevelopment, "prod": EnvProduction, "stage": EnvStaging, "stg": EnvStaging, "test": EnvTest, "local": EnvDevelopment, } if normalized, ok := mappings[envLower]; ok { return normalized } // Si dĂ©jĂ  valide, retourner tel quel if isValidEnvironment(envLower) { return envLower } // Fallback return EnvDevelopment } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestDetectEnvironment(t *testing.T) { tests := []struct { name string setupFunc func() expected string }{ { name: "APP_ENV takes priority", setupFunc: func() { os.Setenv("APP_ENV", "production") os.Setenv("NODE_ENV", "development") }, expected: EnvProduction, }, { name: "NODE_ENV fallback", setupFunc: func() { os.Unsetenv("APP_ENV") os.Setenv("NODE_ENV", "staging") }, expected: EnvStaging, }, { name: "default to development", setupFunc: func() { os.Unsetenv("APP_ENV") os.Unsetenv("NODE_ENV") os.Unsetenv("GO_ENV") }, expected: EnvDevelopment, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.setupFunc() defer func() { os.Unsetenv("APP_ENV") os.Unsetenv("NODE_ENV") os.Unsetenv("GO_ENV") }() result := DetectEnvironment() assert.Equal(t, tt.expected, result) }) } } func TestNormalizeEnvironment(t *testing.T) { tests := []struct { input string expected string }{ {"dev", EnvDevelopment}, {"prod", EnvProduction}, {"stage", EnvStaging}, {"development", EnvDevelopment}, {"invalid", EnvDevelopment}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := NormalizeEnvironment(tt.input) assert.Equal(t, tt.expected, result) }) } } ``` ### Definition of Done - [x] DetectEnvironment() implĂ©mentĂ© avec prioritĂ©s (APP_ENV > NODE_ENV > GO_ENV > hostname > development) - [x] Support APP_ENV, NODE_ENV, GO_ENV avec validation - [x] Fallback intelligent par hostname (dĂ©tection prod/staging) - [x] NormalizeEnvironment() pour normaliser les noms (dev, prod, stage, etc.) - [x] isValidEnvironment() pour validation stricte des environnements - [x] Constantes EnvDevelopment, EnvStaging, EnvProduction, EnvTest dĂ©finies - [x] Tests unitaires créés (10 tests, coverage > 95%) - [x] Tests pour prioritĂ©s, cas limites, alias, validation - [x] IntĂ©grĂ© dans NewConfig() (remplace getEnv("APP_ENV")) - [x] Code review approuvĂ© --- ## T0040: Add Configuration Watch Mode ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-035 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0034 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter mode watch pour surveiller les changements de fichiers de configuration (.env) et recharger automatiquement. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/config/watcher.go` - `veza-backend-api/internal/config/watcher_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/config/config.go` - `veza-backend-api/internal/config/reloader.go` ### ImplĂ©mentation **Étape 1**: Ajouter dĂ©pendance fsnotify **Étape 2**: CrĂ©er ConfigWatcher avec goroutine **Étape 3**: Surveiller .env et .env.{env} **Étape 4**: DĂ©bouncer les Ă©vĂ©nements (500ms) **Étape 5**: IntĂ©grer avec ConfigReloader ### Code Snippets **veza-backend-api/internal/config/watcher.go**: ```go package config import ( "fmt" "path/filepath" "sync" "time" "github.com/fsnotify/fsnotify" "go.uber.org/zap" ) // ConfigWatcher surveille les fichiers de configuration pour changements (T0040) type ConfigWatcher struct { watcher *fsnotify.Watcher reloader *ConfigReloader logger *zap.Logger stopChan chan struct{} wg sync.WaitGroup debounce time.Duration } // NewConfigWatcher crĂ©e un nouveau watcher de configuration func NewConfigWatcher(reloader *ConfigReloader, logger *zap.Logger) (*ConfigWatcher, error) { watcher, err := fsnotify.NewWatcher() if err != nil { return nil, fmt.Errorf("failed to create watcher: %w", err) } return &ConfigWatcher{ watcher: watcher, reloader: reloader, logger: logger, stopChan: make(chan struct{}), debounce: 500 * time.Millisecond, }, nil } // Watch surveille les fichiers .env pour changements func (w *ConfigWatcher) Watch(envFiles []string) error { // Ajouter les fichiers Ă  surveiller for _, file := range envFiles { if err := w.watcher.Add(file); err != nil { w.logger.Warn("Failed to watch file", zap.String("file", file), zap.Error(err)) continue } w.logger.Info("Watching config file", zap.String("file", file)) } w.wg.Add(1) go w.watchLoop() return nil } // watchLoop boucle principale de surveillance func (w *ConfigWatcher) watchLoop() { defer w.wg.Done() timer := time.NewTimer(0) defer timer.Stop() timer.Stop() // Stop immĂ©diatement var lastEventTime time.Time for { select { case event, ok := <-w.watcher.Events: if !ok { return } // Ignorer les opĂ©rations autres que Write if event.Op&fsnotify.Write == 0 { continue } // DĂ©bouncer (attendre 500ms aprĂšs dernier Ă©vĂ©nement) now := time.Now() if now.Sub(lastEventTime) < w.debounce { timer.Reset(w.debounce) continue } lastEventTime = now w.logger.Info("Config file changed, reloading", zap.String("file", event.Name)) // Recharger la configuration if err := w.reloader.ReloadAll(); err != nil { w.logger.Error("Failed to reload config", zap.Error(err)) } else { w.logger.Info("Config reloaded successfully") } case err, ok := <-w.watcher.Errors: if !ok { return } w.logger.Error("Watcher error", zap.Error(err)) case <-timer.C: // Timer expired, reload now w.logger.Info("Debounce expired, reloading config") if err := w.reloader.ReloadAll(); err != nil { w.logger.Error("Failed to reload config", zap.Error(err)) } case <-w.stopChan: return } } } // Stop arrĂȘte la surveillance func (w *ConfigWatcher) Stop() error { close(w.stopChan) err := w.watcher.Close() w.wg.Wait() return err } // GetWatchedFiles retourne la liste des fichiers surveillĂ©s func (w *ConfigWatcher) GetWatchedFiles() []string { return w.watcher.WatchList() } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestConfigWatcher_Watch(t *testing.T) { logger := zap.NewNop() config := &Config{LogLevel: "INFO"} reloader := NewConfigReloader(config, logger) watcher, err := NewConfigWatcher(reloader, logger) require.NoError(t, err) defer watcher.Stop() // CrĂ©er un fichier temporaire tmpFile := filepath.Join(t.TempDir(), ".env.test") err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=DEBUG\n"), 0644) require.NoError(t, err) err = watcher.Watch([]string{tmpFile}) require.NoError(t, err) // Modifier le fichier time.Sleep(100 * time.Millisecond) err = os.WriteFile(tmpFile, []byte("LOG_LEVEL=ERROR\n"), 0644) require.NoError(t, err) // Attendre le debounce + reload time.Sleep(600 * time.Millisecond) // VĂ©rifier que le reload a Ă©tĂ© appelĂ© assert.Equal(t, "ERROR", config.LogLevel) } ``` ### Definition of Done - [x] DĂ©pendance fsnotify ajoutĂ©e (github.com/fsnotify/fsnotify) - [x] ConfigWatcher créé avec watch loop (internal/config/watcher.go) - [x] Support surveillance .env et .env.{env} avec chemins absolus - [x] DĂ©bouncing 500ms implĂ©mentĂ© (Ă©vite reloads multiples) - [x] IntĂ©gration avec ConfigReloader (reload automatique sur changement) - [x] Stop() pour arrĂȘter proprement (ferme watcher et attend goroutine) - [x] GetWatchedFiles() pour lister les fichiers surveillĂ©s - [x] IntĂ©grĂ© dans NewConfig() (activĂ© via CONFIG_WATCH=true) - [x] IntĂ©grĂ© dans Config.Close() (arrĂȘt propre) - [x] Tests unitaires créés (12 tests, coverage > 85%) - [x] Tests pour watch, stop, multiples fichiers, chemins relatifs - [x] Code review approuvĂ© --- ## T0041: Add Integration Test Helpers ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-036 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0013 ✅, T0010 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er helpers pour faciliter l'Ă©criture de tests d'intĂ©gration avec setup/teardown de base de donnĂ©es, serveur HTTP, et clients de test. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/integration.go` - `veza-backend-api/internal/testutils/integration_test.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er SetupIntegrationDB() avec PostgreSQL rĂ©el **Étape 2**: CrĂ©er SetupTestServer() avec Gin router **Étape 3**: CrĂ©er TestClient avec mĂ©thodes helper **Étape 4**: Ajouter CleanupIntegrationDB() **Étape 5**: Tests d'intĂ©gration ### Code Snippets **veza-backend-api/internal/testutils/integration.go**: ```go package testutils import ( "context" "fmt" "net/http" "net/http/httptest" "testing" "veza-backend-api/internal/config" "veza-backend-api/internal/database" "veza-backend-api/internal/routes" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" ) // IntegrationTestSetup contient les ressources pour un test d'intĂ©gration (T0041) type IntegrationTestSetup struct { DB *database.Database Router *gin.Engine Config *config.Config } // SetupIntegrationDB configure une base de donnĂ©es PostgreSQL pour les tests d'intĂ©gration func SetupIntegrationDB(t *testing.T) *database.Database { // Utiliser une base de donnĂ©es de test dĂ©diĂ©e dbURL := GetTestDatabaseURL() dbConfig := &database.Config{ URL: dbURL, MaxOpenConns: 5, MaxIdleConns: 2, MaxLifetime: 5 * time.Minute, MaxIdleTime: 1 * time.Minute, } db, err := database.NewDatabase(dbConfig) require.NoError(t, err, "Failed to setup integration database") // Nettoyer les tables CleanupDatabase(t, db) t.Cleanup(func() { CleanupDatabase(t, db) if err := db.Close(); err != nil { t.Logf("Error closing database: %v", err) } }) return db } // SetupIntegrationTest configure un environnement de test complet (T0041) func SetupIntegrationTest(t *testing.T) *IntegrationTestSetup { // Setup database db := SetupIntegrationDB(t) // Setup config avec valeurs de test testConfig := config.NewTestConfig(t) testConfig.Database = db // Setup router gin.SetMode(gin.TestMode) router := gin.New() // Setup routes (simplifiĂ© pour tests) // routes.SetupRoutes(router, ...) return &IntegrationTestSetup{ DB: db, Router: router, Config: testConfig, } } // TestClient simplifie les appels HTTP dans les tests (T0041) type TestClient struct { server *httptest.Server client *http.Client } // NewTestClient crĂ©e un nouveau client de test func NewTestClient(router *gin.Engine) *TestClient { server := httptest.NewServer(router) return &TestClient{ server: server, client: &http.Client{}, } } // Get fait une requĂȘte GET func (c *TestClient) Get(path string) (*http.Response, error) { return c.client.Get(c.server.URL + path) } // Post fait une requĂȘte POST func (c *TestClient) Post(path, contentType string, body []byte) (*http.Response, error) { return c.client.Post(c.server.URL+path, contentType, body) } // Close ferme le serveur de test func (c *TestClient) Close() { c.server.Close() } // GetTestDatabaseURL retourne l'URL de la base de donnĂ©es de test func GetTestDatabaseURL() string { dbURL := os.Getenv("TEST_DATABASE_URL") if dbURL == "" { return "postgresql://veza:password@localhost:5432/veza_test_db" } return dbURL } // CleanupDatabase nettoie toutes les tables de la base de donnĂ©es func CleanupDatabase(t *testing.T, db *database.Database) { // DĂ©sactiver les foreign keys temporairement db.GormDB.Exec("SET session_replication_role = 'replica'") defer db.GormDB.Exec("SET session_replication_role = 'origin'") // Supprimer toutes les donnĂ©es tables := []string{ "refresh_tokens", "playlist_tracks", "playlists", "tracks", "users", // ... autres tables } for _, table := range tables { if err := db.GormDB.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)).Error; err != nil { t.Logf("Error truncating table %s: %v", table, err) } } } ``` ### Tests Ă  Écrire **Integration Tests**: ```go func TestIntegrationTestSetup(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test") } setup := SetupIntegrationTest(t) defer setup.DB.Close() assert.NotNil(t, setup.DB) assert.NotNil(t, setup.Router) assert.NotNil(t, setup.Config) } func TestTestClient(t *testing.T) { router := gin.New() router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) client := NewTestClient(router) defer client.Close() resp, err := client.Get("/test") require.NoError(t, err) assert.Equal(t, 200, resp.StatusCode) } ``` ### Definition of Done - [x] SetupIntegrationDB() créé avec PostgreSQL rĂ©el (internal/testutils/integration.go) - [x] SetupIntegrationTest() configure environnement complet (DB, Router, Config) - [x] TestClient avec mĂ©thodes Get, Post, Put, Delete, GetWithContext, PostWithContext - [x] CleanupDatabase() pour nettoyer entre tests (TRUNCATE CASCADE avec session_replication_role) - [x] Support flag -short pour skip integration tests (testing.Short()) - [x] GetTestDatabaseURL() avec fallback vers valeur par dĂ©faut - [x] Tests d'intĂ©gration créés (13 tests, coverage > 85%) - [x] Tests pour TestClient (GET, POST, PUT, DELETE, timeout, context) - [x] Tests pour SetupIntegrationTest et SetupIntegrationDB - [x] Tests pour CleanupDatabase - [x] Code review approuvĂ© --- ## T0042: Add Mock Helpers for Services ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-037 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er helpers pour gĂ©nĂ©rer des mocks de services (SessionService, AuditService, etc.) avec testify/mock pour faciliter les tests unitaires. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/mocks.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: GĂ©nĂ©rer interfaces pour tous les services **Étape 2**: CrĂ©er NewMockSessionService() helper **Étape 3**: CrĂ©er NewMockAuditService() helper **Étape 4**: Ajouter mĂ©thodes helper pour setup expectations **Étape 5**: Tests avec mocks ### Code Snippets **veza-backend-api/internal/testutils/mocks.go**: ```go package testutils import ( "time" "veza-backend-api/internal/services" "github.com/google/uuid" "github.com/stretchr/testify/mock" ) // MockSessionService est un mock pour SessionService (T0042) type MockSessionService struct { mock.Mock } // NewMockSessionService crĂ©e un nouveau mock SessionService func NewMockSessionService() *MockSessionService { return &MockSessionService{} } // CreateSession mock func (m *MockSessionService) CreateSession(userID uuid.UUID, ipAddress string) (*services.Session, error) { args := m.Called(userID, ipAddress) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*services.Session), args.Error(1) } // GetSession mock func (m *MockSessionService) GetSession(sessionID uuid.UUID) (*services.Session, error) { args := m.Called(sessionID) if args.Get(0) == nil { return nil, args.Error(1) } return args.Get(0).(*services.Session), args.Error(1) } // MockAuditService est un mock pour AuditService (T0042) type MockAuditService struct { mock.Mock } // NewMockAuditService crĂ©e un nouveau mock AuditService func NewMockAuditService() *MockAuditService { return &MockAuditService{} } // LogAction mock func (m *MockAuditService) LogAction(userID uuid.UUID, action string, details map[string]interface{}) error { args := m.Called(userID, action, details) return args.Error(0) } // SetupMockSessionSuccess configure un mock pour succĂšs func SetupMockSessionSuccess(mockService *MockSessionService, userID uuid.UUID) { session := &services.Session{ ID: uuid.New(), UserID: userID, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(24 * time.Hour), } mockService.On("CreateSession", userID, mock.Anything).Return(session, nil) } // SetupMockAuditSuccess configure un mock audit pour succĂšs func SetupMockAuditSuccess(mockService *MockAuditService) { mockService.On("LogAction", mock.Anything, mock.Anything, mock.Anything).Return(nil) } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestMockSessionService(t *testing.T) { mockService := NewMockSessionService() userID := uuid.New() SetupMockSessionSuccess(mockService, userID) session, err := mockService.CreateSession(userID, "127.0.0.1") require.NoError(t, err) assert.NotNil(t, session) assert.Equal(t, userID, session.UserID) mockService.AssertExpectations(t) } ``` ### Definition of Done - [x] MockSessionService créé avec toutes les mĂ©thodes (CreateSession, ValidateSession, RevokeSession, etc.) - [x] MockAuditService créé avec toutes les mĂ©thodes (LogAction, LogLogin, LogLogout, LogUpload, etc.) - [x] Helper functions SetupMock* pour faciliter setup (SetupMockSessionSuccess, SetupMockAuditSuccess, etc.) - [x] Helpers pour cas d'erreur (SetupMockSessionValidationError, SetupMockAuditSearchLogsError) - [x] Tests unitaires créés (19 tests, coverage > 90%) - [x] Tests pour toutes les mĂ©thodes des mocks - [x] Tests pour helpers SetupMock* - [x] Code review approuvĂ© --- ## T0043: Add Test Coverage Reporting ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-038 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer gĂ©nĂ©ration de rapports de coverage avec format HTML et JSON, intĂ©gration CI/CD, et seuil minimum de 80%. ### Fichiers Ă  CrĂ©er - `scripts/test-coverage.sh` - `.github/workflows/test-coverage.yml` (si GitHub Actions) ### Fichiers Ă  Modifier - `Makefile` (ajouter target coverage) - `veza-backend-api/go.mod` (gocovmerge si nĂ©cessaire) ### ImplĂ©mentation **Étape 1**: CrĂ©er script test-coverage.sh **Étape 2**: GĂ©nĂ©rer coverage avec -coverprofile **Étape 3**: GĂ©nĂ©rer HTML avec go tool cover **Étape 4**: VĂ©rifier seuil 80% **Étape 5**: IntĂ©grer dans CI/CD ### Code Snippets **scripts/test-coverage.sh**: ```bash #!/bin/bash # Script pour gĂ©nĂ©rer et vĂ©rifier le coverage de tests (T0043) set -e COVERAGE_DIR="coverage" COVERAGE_PROFILE="$COVERAGE_DIR/coverage.out" COVERAGE_HTML="$COVERAGE_DIR/coverage.html" COVERAGE_THRESHOLD=80 # CrĂ©er le dossier coverage mkdir -p "$COVERAGE_DIR" # GĂ©nĂ©rer le profile de coverage echo "Running tests with coverage..." go test ./... -coverprofile="$COVERAGE_PROFILE" -covermode=atomic # GĂ©nĂ©rer le rapport HTML echo "Generating HTML report..." go tool cover -html="$COVERAGE_PROFILE" -o "$COVERAGE_HTML" # Calculer le pourcentage de coverage COVERAGE_PERCENT=$(go tool cover -func="$COVERAGE_PROFILE" | grep total | awk '{print $3}' | sed 's/%//' | cut -d. -f1) echo "Total coverage: ${COVERAGE_PERCENT}%" # VĂ©rifier le seuil if [ "$COVERAGE_PERCENT" -lt "$COVERAGE_THRESHOLD" ]; then echo "ERROR: Coverage ${COVERAGE_PERCENT}% is below threshold ${COVERAGE_THRESHOLD}%" exit 1 fi echo "Coverage check passed!" ``` **Makefile** (ajout): ```makefile .PHONY: test-coverage test-coverage: @bash scripts/test-coverage.sh .PHONY: coverage-html coverage-html: @go tool cover -html=coverage/coverage.out -o coverage/coverage.html @echo "Coverage report generated: coverage/coverage.html" ``` ### Tests Ă  Écrire **Manual Tests**: ```bash # ExĂ©cuter make test-coverage # VĂ©rifier que le rapport HTML est gĂ©nĂ©rĂ© open coverage/coverage.html ``` ### Definition of Done - [x] Script test-coverage.sh créé (scripts/test-coverage.sh) - [x] GĂ©nĂ©ration de coverage.out avec -coverprofile et -covermode=atomic - [x] GĂ©nĂ©ration de coverage.html avec go tool cover - [x] GĂ©nĂ©ration de coverage.json avec rĂ©sumĂ© (optionnel) - [x] VĂ©rification seuil 80% avec exit code (Ă©choue si < 80%) - [x] IntĂ©grĂ© dans Makefile (target test-coverage et coverage-html) - [x] Script exĂ©cutable avec permissions (chmod +x) - [x] Gestion des chemins relatifs et rĂ©pertoires de travail - [x] IntĂ©gration CI/CD GitHub Actions (.github/workflows/test-coverage.yml) - [x] Workflow avec upload d'artifacts et commentaire PR optionnel - [x] Code review approuvĂ© --- ## T0044: Add Benchmark Testing Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-039 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er utilities pour faciliter l'Ă©criture de benchmarks de performance avec helpers pour setup/teardown et comparaisons. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/benchmark.go` - `veza-backend-api/internal/benchmarks/example_test.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er SetupBenchmarkDB() pour benchmarks **Étape 2**: CrĂ©er helper RunBenchmarkWithSetup() **Étape 3**: Ajouter exemples de benchmarks **Étape 4**: Documentation des patterns **Étape 5**: Tests de benchmarks ### Code Snippets **veza-backend-api/internal/testutils/benchmark.go**: ```go package testutils import ( "testing" "veza-backend-api/internal/database" ) // BenchmarkSetup contient les ressources pour un benchmark (T0044) type BenchmarkSetup struct { DB *database.Database } // SetupBenchmarkDB configure une DB pour benchmarks func SetupBenchmarkDB(b *testing.B) *database.Database { dbURL := GetTestDatabaseURL() dbConfig := &database.Config{ URL: dbURL, MaxOpenConns: 10, MaxIdleConns: 5, } db, err := database.NewDatabase(dbConfig) if err != nil { b.Fatalf("Failed to setup benchmark database: %v", err) } b.Cleanup(func() { if err := db.Close(); err != nil { b.Logf("Error closing database: %v", err) } }) return db } // RunBenchmarkWithSetup exĂ©cute un benchmark avec setup/teardown (T0044) func RunBenchmarkWithSetup(b *testing.B, setup func(*testing.B) interface{}, benchFunc func(*testing.B, interface{}), teardown func(*testing.B, interface{})) { setupResult := setup(b) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { benchFunc(b, setupResult) } }) if teardown != nil { teardown(b, setupResult) } } // BenchmarkExample exemple de benchmark (T0044) func BenchmarkExample(b *testing.B) { setup := SetupBenchmarkDB(b) b.ResetTimer() for i := 0; i < b.N; i++ { // Code Ă  benchmarker _ = setup } } ``` **veza-backend-api/internal/benchmarks/example_test.go**: ```go package benchmarks import ( "testing" "veza-backend-api/internal/testutils" ) func BenchmarkDatabaseQuery(b *testing.B) { db := testutils.SetupBenchmarkDB(b) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { // Exemple de requĂȘte var count int64 db.GormDB.Raw("SELECT COUNT(*) FROM users").Scan(&count) } }) } ``` ### Tests Ă  Écrire **Benchmark Tests**: ```bash # ExĂ©cuter tous les benchmarks go test -bench=. -benchmem ./internal/benchmarks/... # ExĂ©cuter un benchmark spĂ©cifique go test -bench=BenchmarkDatabaseQuery -benchmem ./internal/benchmarks/... ``` ### Definition of Done - [x] SetupBenchmarkDB() créé avec configuration optimisĂ©e pour benchmarks - [x] RunBenchmarkWithSetup() helper créé pour setup/teardown automatique - [x] BenchmarkExample() exemple fourni dans benchmark.go - [x] Exemples de benchmarks fournis (DatabaseQuery, Sequential, SimpleQuery) - [x] Support pour RunParallel et benchmarks sĂ©quentiels - [x] Tests de benchmarks fonctionnels (peuvent ĂȘtre exĂ©cutĂ©s avec go test -bench) - [x] Code review approuvĂ© --- ## T0045: Add Table-Driven Test Helpers ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-040 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er helpers pour faciliter l'Ă©criture de tests table-driven avec assertions simplifiĂ©es et reporting d'erreurs amĂ©liorĂ©. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/table_test.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er RunTableTests() helper **Étape 2**: CrĂ©er RunTableSubTests() avec subtests **Étape 3**: Ajouter helpers pour assertions communes **Étape 4**: Documentation avec exemples **Étape 5**: Tests des helpers ### Code Snippets **veza-backend-api/internal/testutils/table_test.go**: ```go package testutils import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // TableTestCase reprĂ©sente un cas de test dans une table-driven test (T0045) type TableTestCase struct { Name string Input interface{} Expected interface{} ExpectedErr error SetupFunc func() interface{} CleanupFunc func(interface{}) } // RunTableTests exĂ©cute une sĂ©rie de tests table-driven (T0045) func RunTableTests(t *testing.T, testCases []TableTestCase, testFunc func(t *testing.T, tc TableTestCase)) { for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { var setupResult interface{} if tc.SetupFunc != nil { setupResult = tc.SetupFunc() } if tc.CleanupFunc != nil { defer tc.CleanupFunc(setupResult) } testFunc(t, tc) }) } } // AssertEqual helper pour assertions Ă©gales func AssertEqual(t *testing.T, expected, actual interface{}, msgAndArgs ...interface{}) { assert.Equal(t, expected, actual, msgAndArgs...) } // RequireNoError helper pour vĂ©rifier absence d'erreur func RequireNoError(t *testing.T, err error, msgAndArgs ...interface{}) { require.NoError(t, err, msgAndArgs...) } // Example usage: /* func TestExample(t *testing.T) { testCases := []TableTestCase{ { Name: "valid input", Input: 42, Expected: "42", }, { Name: "invalid input", Input: -1, ExpectedErr: errors.New("negative not allowed"), }, } RunTableTests(t, testCases, func(t *testing.T, tc TableTestCase) { result, err := ProcessInput(tc.Input.(int)) if tc.ExpectedErr != nil { assert.Error(t, err) return } RequireNoError(t, err) AssertEqual(t, tc.Expected, result) }) } */ ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestRunTableTests(t *testing.T) { testCases := []TableTestCase{ { Name: "test case 1", Input: 1, Expected: 2, }, { Name: "test case 2", Input: 2, Expected: 4, }, } RunTableTests(t, testCases, func(t *testing.T, tc TableTestCase) { result := tc.Input.(int) * 2 AssertEqual(t, tc.Expected, result) }) } ``` ### Definition of Done - [x] TableTestCase struct créé avec champs Input, Expected, ExpectedErr, SetupFunc, CleanupFunc - [x] RunTableTests() helper créé avec support setup/cleanup - [x] RunTableSubTests() helper créé pour sous-tests - [x] AssertEqual, AssertNotEqual helpers créés - [x] RequireNoError, RequireError helpers créés - [x] AssertNil, AssertNotNil helpers créés - [x] AssertTrue, AssertFalse helpers créés - [x] Documentation avec exemples d'utilisation en commentaires - [x] Tests unitaires créés (14 tests, coverage ≄ 80%) - [x] Tests pour tous les helpers d'assertion - [x] Code review approuvĂ© --- ## T0046: Add Golden File Testing Support ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-041 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter support pour golden file testing (comparaison avec fichiers de rĂ©fĂ©rence) pour tests de formatage, sĂ©rialisation, etc. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/golden.go` - `veza-backend-api/testdata/` (directory) ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er UpdateGoldenFile() helper **Étape 2**: CrĂ©er CompareGoldenFile() helper **Étape 3**: Support flag -update pour mettre Ă  jour **Étape 4**: Ajouter exemples **Étape 5**: Tests des helpers ### Code Snippets **veza-backend-api/internal/testutils/golden.go**: ```go package testutils import ( "flag" "os" "path/filepath" "testing" "github.com/stretchr/testify/require" ) var updateGolden = flag.Bool("update", false, "update golden files") // GetGoldenFilePath retourne le chemin vers un fichier golden (T0046) func GetGoldenFilePath(t *testing.T, filename string) string { return filepath.Join("testdata", t.Name()+"_"+filename) } // UpdateGoldenFile met Ă  jour un fichier golden (T0046) func UpdateGoldenFile(t *testing.T, filename string, content []byte) { if !*updateGolden { t.Skip("Skipping golden file update (use -update flag)") return } path := GetGoldenFilePath(t, filename) err := os.MkdirAll(filepath.Dir(path), 0755) require.NoError(t, err) err = os.WriteFile(path, content, 0644) require.NoError(t, err) } // CompareGoldenFile compare le contenu avec un fichier golden (T0046) func CompareGoldenFile(t *testing.T, filename string, actual []byte) { path := GetGoldenFilePath(t, filename) // Si update flag, mettre Ă  jour if *updateGolden { UpdateGoldenFile(t, filename, actual) return } // Lire le fichier golden expected, err := os.ReadFile(path) require.NoError(t, err, "Golden file not found. Run tests with -update flag to create it.") require.Equal(t, string(expected), string(actual), "Golden file mismatch") } // Example usage: /* func TestJSONOutput(t *testing.T) { data := map[string]interface{}{ "key": "value", } jsonBytes, _ := json.MarshalIndent(data, "", " ") CompareGoldenFile(t, "output.json", jsonBytes) } */ ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestGoldenFile(t *testing.T) { content := []byte("test content") // CrĂ©er le fichier golden si n'existe pas if *updateGolden { UpdateGoldenFile(t, "test.txt", content) } // Comparer CompareGoldenFile(t, "test.txt", content) } ``` ### Definition of Done - [x] GetGoldenFilePath() créé pour gĂ©nĂ©rer les chemins de fichiers golden - [x] UpdateGoldenFile() avec flag -update pour mettre Ă  jour les fichiers - [x] CompareGoldenFile() pour comparaison avec fichiers golden - [x] Support directory testdata/ créé avec .gitkeep - [x] Flag -update pour mise Ă  jour des fichiers golden - [x] Gestion automatique de la crĂ©ation de rĂ©pertoires - [x] Documentation avec exemples d'utilisation en commentaires - [x] Tests unitaires créés (5 tests, coverage ≄ 80%) - [x] Tests pour cas normaux, mismatch, update et fichier non trouvĂ© - [x] Code review approuvĂ© --- ## T0047: Add Test Fixtures Generator ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-042 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er gĂ©nĂ©rateur de fixtures de test avec factory pattern pour crĂ©er des donnĂ©es de test rĂ©alistes et variĂ©es. ### Fichiers Ă  Modifier - `veza-backend-api/internal/testutils/fixtures.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er UserFactory avec mĂ©thodes Builder **Étape 2**: CrĂ©er TrackFactory, PlaylistFactory, etc. **Étape 3**: Ajouter mĂ©thodes With*() pour customisation **Étape 4**: Ajouter Build() et MustBuild() **Étape 5**: Tests des factories ### Code Snippets **veza-backend-api/internal/testutils/fixtures.go** (additions): ```go package testutils import ( "veza-backend-api/internal/models" "github.com/google/uuid" ) // UserFactory crĂ©e des utilisateurs de test (T0047) type UserFactory struct { user *models.User } // NewUserFactory crĂ©e un nouveau factory func NewUserFactory() *UserFactory { return &UserFactory{ user: &models.User{ ID: uuid.New(), Username: "testuser", Email: "test@example.com", PasswordHash: "hashed_password", Role: "user", TokenVersion: 0, }, } } // WithUsername dĂ©finit le username func (f *UserFactory) WithUsername(username string) *UserFactory { f.user.Username = username return f } // WithEmail dĂ©finit l'email func (f *UserFactory) WithEmail(email string) *UserFactory { f.user.Email = email return f } // WithRole dĂ©finit le rĂŽle func (f *UserFactory) WithRole(role string) *UserFactory { f.user.Role = role return f } // Build construit l'utilisateur func (f *UserFactory) Build() *models.User { return f.user } // MustBuild construit et sauvegarde en DB (T0047) func (f *UserFactory) MustBuild(db *gorm.DB) *models.User { user := f.Build() if err := db.Create(user).Error; err != nil { panic(err) } return user } // CreateUsers crĂ©e N utilisateurs func CreateUsers(db *gorm.DB, count int) []*models.User { users := make([]*models.User, count) for i := 0; i < count; i++ { factory := NewUserFactory(). WithUsername(fmt.Sprintf("user%d", i)). WithEmail(fmt.Sprintf("user%d@example.com", i)) users[i] = factory.MustBuild(db) } return users } ``` ### Tests Ă  Écrire **Unit Tests**: ```go func TestUserFactory(t *testing.T) { factory := NewUserFactory(). WithUsername("admin"). WithEmail("admin@example.com"). WithRole("admin") user := factory.Build() assert.Equal(t, "admin", user.Username) assert.Equal(t, "admin@example.com", user.Email) assert.Equal(t, "admin", user.Role) } ``` ### Definition of Done - [x] UserFactory avec mĂ©thodes Builder créé (WithUsername, WithEmail, WithRole, etc.) - [x] TrackFactory créé avec mĂ©thodes WithTitle, WithArtist, WithDescription, WithDuration - [x] PlaylistFactory créé avec mĂ©thodes WithName, WithDescription - [x] Build() pour construction sans sauvegarde - [x] MustBuild() pour sauvegarde automatique en DB - [x] CreateUsers() helper créé pour crĂ©er N utilisateurs - [x] CreateTracks() helper créé pour crĂ©er N tracks - [x] Support pour tous les champs personnalisables avec mĂ©thodes With* - [x] Documentation avec exemples d'utilisation - [x] Tests unitaires créés (12 tests, coverage ≄ 80%) - [x] Tests pour toutes les factories et leurs mĂ©thodes - [x] Code review approuvĂ© --- ## T0048: Add Test Parallel Execution Helpers ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-043 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er helpers pour faciliter l'exĂ©cution parallĂšle de tests avec isolation de donnĂ©es et gestion de ressources partagĂ©es. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/parallel.go` ### Fichiers Ă  Modifier - Aucun ### ImplĂ©mentation **Étape 1**: CrĂ©er SetupParallelTest() avec isolation **Étape 2**: CrĂ©er helpers pour locks partagĂ©s **Étape 3**: Ajouter documentation sur parallĂ©lisation **Étape 4**: Exemples de tests parallĂšles **Étape 5**: Tests des helpers ### Code Snippets **veza-backend-api/internal/testutils/parallel.go**: ```go package testutils import ( "sync" "testing" ) var ( parallelLock sync.Mutex ) // SetupParallelTest configure un test pour exĂ©cution parallĂšle (T0048) func SetupParallelTest(t *testing.T) { t.Parallel() // AcquĂ©rir un lock si ressources partagĂ©es // parallelLock.Lock() // t.Cleanup(func() { parallelLock.Unlock() }) } // RunParallelTests exĂ©cute plusieurs tests en parallĂšle (T0048) func RunParallelTests(t *testing.T, testFuncs map[string]func(*testing.T)) { var wg sync.WaitGroup for name, fn := range testFuncs { wg.Add(1) go func(name string, fn func(*testing.T)) { defer wg.Done() t.Run(name, func(t *testing.T) { t.Parallel() fn(t) }) }(name, fn) } wg.Wait() } // Example usage: /* func TestParallel(t *testing.T) { testFuncs := map[string]func(*testing.T){ "test1": func(t *testing.T) { SetupParallelTest(t) // Test code }, "test2": func(t *testing.T) { SetupParallelTest(t) // Test code }, } RunParallelTests(t, testFuncs) } */ ``` ### Definition of Done - [x] SetupParallelTest() créé avec support t.Parallel() - [x] RunParallelTests() helper créé pour exĂ©cuter plusieurs tests en parallĂšle - [x] WithLock() helper créé pour exĂ©cuter des fonctions avec lock partagĂ© - [x] TestLockManager créé pour gĂ©rer des locks nommĂ©s - [x] Support locks pour ressources partagĂ©es (parallelLock, TestLockManager) - [x] Documentation avec exemples d'utilisation en commentaires - [x] Tests unitaires créés (8 tests, coverage ≄ 80%) - [x] Tests pour exĂ©cution parallĂšle, locks partagĂ©s et locks nommĂ©s - [x] Code review approuvĂ© --- ## T0049: Add Test Data Cleanup Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-044 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0013 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique AmĂ©liorer les utilities de nettoyage de donnĂ©es de test avec support cascade, transactions, et hooks de cleanup. ### Fichiers Ă  Modifier - `veza-backend-api/internal/testutils/db.go` ### ImplĂ©mentation **Étape 1**: AmĂ©liorer CleanupDatabase() avec cascade **Étape 2**: Ajouter CleanupWithTransaction() **Étape 3**: Ajouter RegisterCleanupHook() **Étape 4**: Support cleanup conditionnel **Étape 5**: Tests de cleanup ### Code Snippets **veza-backend-api/internal/testutils/db.go** (additions): ```go package testutils // CleanupOptions configure le comportement du cleanup (T0049) type CleanupOptions struct { Cascade bool UseTransaction bool SkipForeignKeys bool } // CleanupDatabaseWithOptions nettoie avec options (T0049) func CleanupDatabaseWithOptions(t *testing.T, db *database.Database, opts CleanupOptions) { if opts.UseTransaction { tx := db.GormDB.Begin() defer tx.Rollback() cleanupTables(t, tx, opts) } else { cleanupTables(t, db.GormDB, opts) } } func cleanupTables(t *testing.T, db *gorm.DB, opts CleanupOptions) { if !opts.SkipForeignKeys { db.Exec("SET session_replication_role = 'replica'") defer db.Exec("SET session_replication_role = 'origin'") } tables := getAllTables(db) for _, table := range tables { if opts.Cascade { db.Exec(fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)) } else { db.Exec(fmt.Sprintf("TRUNCATE TABLE %s", table)) } } } // RegisterCleanupHook enregistre un hook de cleanup (T0049) func RegisterCleanupHook(t *testing.T, hook func()) { t.Cleanup(hook) } ``` ### Definition of Done - [x] CleanupOptions struct créé avec Cascade, UseTransaction, SkipForeignKeys, Tables - [x] CleanupDatabaseWithOptions() avec options configurables - [x] Support cascade pour PostgreSQL (CASCADE dans TRUNCATE) - [x] Support transactions avec rollback automatique - [x] Support pour SQLite et PostgreSQL (dĂ©tection automatique) - [x] getAllTables() pour dĂ©tecter automatiquement les tables - [x] getDefaultTables() pour liste de fallback - [x] RegisterCleanupHook() pour hooks personnalisĂ©s - [x] CleanupWithTransaction() pour cleanup avec transaction - [x] CleanupSpecificTables() pour nettoyer tables spĂ©cifiques - [x] Tests unitaires créés (9 tests, coverage ≄ 80%) - [x] Tests pour toutes les options de cleanup - [x] Code review approuvĂ© --- ## T0050: Add Test Performance Monitoring ✅ COMPLÉTÉE **Feature Parente**: FEAT-INFRA-045 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0043 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter monitoring de performance des tests avec tracking de durĂ©e, dĂ©tection de tests lents, et rapports. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/testutils/performance.go` - `scripts/test-performance.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er TestTimer helper **Étape 2**: CrĂ©er script pour dĂ©tecter tests lents **Étape 3**: Ajouter reporting de performance **Étape 4**: IntĂ©grer dans CI/CD **Étape 5**: Tests de monitoring ### Code Snippets **veza-backend-api/internal/testutils/performance.go**: ```go package testutils import ( "testing" "time" ) // TestTimer mesure la durĂ©e d'un test (T0050) type TestTimer struct { start time.Time t *testing.T } // StartTimer dĂ©marre un timer de test func StartTimer(t *testing.T) *TestTimer { return &TestTimer{ start: time.Now(), t: t, } } // Stop arrĂȘte le timer et log la durĂ©e func (tt *TestTimer) Stop() time.Duration { duration := time.Since(tt.start) tt.t.Logf("Test duration: %v", duration) return duration } // WarnIfSlow avertit si le test est lent (T0050) func (tt *TestTimer) WarnIfSlow(threshold time.Duration) time.Duration { duration := tt.Stop() if duration > threshold { tt.t.Logf("WARNING: Test took %v (threshold: %v)", duration, threshold) } return duration } // Example usage: /* func TestSlowOperation(t *testing.T) { timer := StartTimer(t) defer timer.WarnIfSlow(5 * time.Second) // Test code } */ ``` **scripts/test-performance.sh**: ```bash #!/bin/bash # DĂ©tecte les tests lents (T0050) THRESHOLD=5s go test ./... -json | jq -r 'select(.Action == "pass" or .Action == "fail") | "\(.Elapsed) \(.Test)"' | \ while read duration test; do if (( $(echo "$duration > $THRESHOLD" | bc -l) )); then echo "SLOW TEST: $test took $duration" fi done ``` ### Definition of Done - [x] TestTimer helper créé avec StartTimer() et StartNamedTimer() - [x] Stop() pour arrĂȘter le timer et logger la durĂ©e - [x] WarnIfSlow() pour dĂ©tecter tests lents avec seuil configurable - [x] Elapsed() pour obtenir la durĂ©e sans arrĂȘter le timer - [x] Reset() pour rĂ©initialiser le timer - [x] Script test-performance.sh créé avec support jq et fallback - [x] DĂ©tection automatique de tests > seuil (configurable via TEST_PERFORMANCE_THRESHOLD) - [x] RĂ©sumĂ© avec compteurs (total tests, slow tests, durĂ©e moyenne/totale) - [x] Code de retour d'erreur si tests lents dĂ©tectĂ©s - [x] Documentation avec exemples d'utilisation en commentaires - [x] Tests unitaires créés (10 tests, coverage ≄ 80%) - [x] Tests pour toutes les mĂ©thodes de TestTimer - [x] Code review approuvĂ© --- *[Phase 1 Configuration Management et Testing Infrastructure complĂ©tĂ©es. Continue avec T0051-T0100...]* --- ## T0051: Fix Chat Server SQLx Compilation Errors ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-001 **Phase**: 1 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0001 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique RĂ©soudre erreurs compilation SQLx dans chat server. RĂ©gĂ©nĂ©rer metadata SQLx, aligner queries avec schĂ©ma DB, fixer types Rust (Uuid vs i32). ### Fichiers Ă  Modifier - `veza-chat-server/src/lib.rs` - `veza-chat-server/src/repository/message_repository.rs` - `veza-chat-server/src/repository/room_repository.rs` - `veza-chat-server/src/models/message.rs` - `veza-chat-server/.sqlx/` (metadata) ### ImplĂ©mentation **Étape 1**: ExĂ©cuter `cargo sqlx prepare --database-url=...` pour rĂ©gĂ©nĂ©rer metadata **Étape 2**: Fixer types dans queries (Uuid pas i32 pour IDs) **Étape 3**: Aligner noms colonnes avec schĂ©ma PostgreSQL **Étape 4**: Fixer casting enums PostgreSQL **Étape 5**: VĂ©rifier compilation `cargo build --release` ### Code Snippets **veza-chat-server/src/repository/message_repository.rs** (example): ```rust use sqlx::{PgPool, Result}; use uuid::Uuid; use chrono::{DateTime, Utc}; use crate::models::{Message, MessageType}; pub struct MessageRepository { pool: PgPool, } impl MessageRepository { pub async fn create( &self, room_id: Uuid, sender_id: Uuid, content: &str, ) -> Result { let message = sqlx::query_as!( Message, r#" INSERT INTO messages (room_id, sender_id, content, message_type, created_at) VALUES ($1, $2, $3, 'text', NOW()) RETURNING id, room_id, sender_id, content, message_type, created_at "#, room_id, sender_id, content ) .fetch_one(&self.pool) .await?; Ok(message) } } ``` ### Definition of Done - [x] Erreurs compilation SQLx rĂ©solues - [x] Queries alignĂ©es avec schĂ©ma PostgreSQL (conversation_id au lieu de room_id) - [x] Types alignĂ©s (Uuid pour IDs, VARCHAR(50) pour message_type) - [x] MessageRepository corrigĂ© (conversation_id, is_deleted) - [x] RoomRepository corrigĂ© (conversations, conversation_members) - [x] MessageType enum ajustĂ© (sans Type derive, utilise VARCHAR en DB) - [x] `cargo check` et `cargo build --release` rĂ©ussissent - [x] Code review approuvĂ© --- ## T0052: Fix Chat Server Duplicate Module Declaration ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-001 **Phase**: 1 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 15min **DĂ©pendances**: T0051 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Supprimer dĂ©claration module dupliquĂ©e dans `lib.rs` (ligne 53 probablement). ### Fichiers Ă  Modifier - `veza-chat-server/src/lib.rs` ### ImplĂ©mentation **Étape 1**: Identifier dĂ©claration module dupliquĂ©e **Étape 2**: Supprimer duplication **Étape 3**: VĂ©rifier compilation ### Code Snippets **veza-chat-server/src/lib.rs** (fix): ```rust // AVANT (duplication) pub mod error; pub mod websocket; pub mod error; // ❌ Duplication // APRÈS pub mod error; pub mod websocket; // ✅ DĂ©claration unique ``` ### Definition of Done - [x] VĂ©rification complĂšte de lib.rs effectuĂ©e - aucune duplication trouvĂ©e - [x] Tous les modules dĂ©clarĂ©s une seule fois (error, simple_message_store, websocket, repository, models) - [x] Compilation rĂ©ussit sans erreurs (`cargo check` et `cargo build --release`) - [x] Tous modules correctement dĂ©clarĂ©s et utilisables - [x] Code review approuvĂ© **Note**: Aucune dĂ©claration module dupliquĂ©e n'a Ă©tĂ© trouvĂ©e dans `lib.rs`. Le fichier est correct avec 5 modules dĂ©clarĂ©s une seule fois chacun. La compilation rĂ©ussit sans erreurs. --- ## T0053: Fix Chat Server Missing Imports ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-001 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0051 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter imports manquants dans chat server (HashMap, trace, etc.). ### Fichiers Ă  Modifier - `veza-chat-server/src/websocket.rs` - `veza-chat-server/src/services.rs` - Autres fichiers avec erreurs imports ### ImplĂ©mentation **Étape 1**: Identifier imports manquants via `cargo check` **Étape 2**: Ajouter imports nĂ©cessaires **Étape 3**: VĂ©rifier compilation ### Code Snippets **veza-chat-server/src/websocket.rs** (example): ```rust use std::collections::HashMap; // ✅ Ajouter si manquant use tracing::{trace, debug, info, error}; // ✅ Ajouter si manquant use uuid::Uuid; use tokio::sync::RwLock; ``` ### Definition of Done - [x] VĂ©rification complĂšte effectuĂ©e via `cargo check` - [x] Import `tracing::warn` ajoutĂ© dans `services.rs` - [x] Tous imports manquants ajoutĂ©s - [x] Compilation rĂ©ussit sans erreurs (`cargo check` et `cargo build --release`) - [x] Code review approuvĂ© **Note**: Le code compilait dĂ©jĂ , mais l'import explicite de `tracing::warn` a Ă©tĂ© ajoutĂ© dans `services.rs` pour la clartĂ© du code. Tous les autres fichiers avaient dĂ©jĂ  leurs imports corrects. --- ## T0054: Align Chat Server Message Store with Database Schema ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0051 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Aligner MessageStore queries SQLx avec schĂ©ma PostgreSQL rĂ©el (colonnes, types, contraintes). ### Fichiers Ă  Modifier - `veza-chat-server/src/repository/message_repository.rs` - `veza-chat-server/src/models/message.rs` ### ImplĂ©mentation **Étape 1**: Examiner schĂ©ma PostgreSQL `messages` table **Étape 2**: Comparer avec struct Rust `Message` **Étape 3**: Aligner colonnes (noms, types, nullabilitĂ©) **Étape 4**: Mettre Ă  jour queries SQLx **Étape 5**: Tests intĂ©gration ### Code Snippets **veza-chat-server/src/models/message.rs**: ```rust use sqlx::FromRow; use uuid::Uuid; use chrono::{DateTime, Utc}; #[derive(Debug, Clone, FromRow)] pub struct Message { pub id: Uuid, pub room_id: Uuid, pub sender_id: Uuid, pub content: String, pub message_type: String, // ou enum pub created_at: DateTime, } ``` ### Definition of Done - [x] Struct Message alignĂ© avec schĂ©ma DB (migrations 001 et 002) - [x] Toutes les colonnes du schĂ©ma intĂ©grĂ©es (conversation_id, parent_message_id, reply_to_id, is_pinned, is_edited, is_deleted, edited_at, status, metadata) - [x] Queries SQLx utilisent noms colonnes corrects - [x] Types Rust correspondent types PostgreSQL (Uuid, bool, Option, String, DateTime, JSONB) - [x] MessageRepository mis Ă  jour (create, get_conversation_messages, get_room_messages alias) - [x] Compilation rĂ©ussit (`cargo check` et `cargo build --release`) - [x] Code review approuvĂ© **DĂ©tails des changements**: - `Message` struct: ajout de toutes les colonnes manquantes (parent_message_id, reply_to_id, is_pinned, is_edited, edited_at, status, metadata) - `Message.conversation_id`: renommĂ© de `room_id` pour correspondre au schĂ©ma DB - `Message.is_deleted`: remplace `deleted_at` pour correspondre au schĂ©ma DB - `MessageRepository.create()`: utilise toutes les colonnes du schĂ©ma avec valeurs par dĂ©faut - `MessageRepository.get_conversation_messages()`: nouvelle mĂ©thode qui retourne toutes les colonnes - `MessageRepository.get_room_messages()`: alias pour compatibilitĂ© avec code existant --- ## T0055: Fix Chat Server Structured Logging Imports ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-001 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 20min **DĂ©pendances**: T0053 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter imports manquants dans `structured_logging.rs` (HashMap, trace, etc.). ### Fichiers Ă  Modifier - `veza-chat-server/src/structured_logging.rs` ### ImplĂ©mentation **Étape 1**: Examiner erreurs compilation dans structured_logging.rs **Étape 2**: Ajouter imports std::collections::HashMap **Étape 3**: Ajouter imports tracing::trace si nĂ©cessaire **Étape 4**: VĂ©rifier compilation ### Code Snippets **veza-chat-server/src/structured_logging.rs**: ```rust use std::collections::HashMap; // ✅ Ajouter use tracing::{trace, debug, info, warn, error}; // ✅ Ajouter si nĂ©cessaire ``` ### Definition of Done - [x] VĂ©rification complĂšte de structured_logging.rs effectuĂ©e - [x] Imports HashMap dĂ©jĂ  prĂ©sents (`use std::collections::HashMap;` ligne 13) - [x] Imports tracing dĂ©jĂ  prĂ©sents (`use tracing::{debug, error, info, trace, warn};` ligne 16) - [x] Module `chat_logs` redĂ©clare ses imports (normal pour sous-module) - [x] Compilation rĂ©ussit (`cargo check` et `cargo build --release`) - [x] Code review approuvĂ© **Note**: Tous les imports nĂ©cessaires Ă©taient dĂ©jĂ  prĂ©sents dans le fichier. Le fichier est correct et ne nĂ©cessitait aucune modification. --- ## T0056: Add Chat Server Database Connection Pool ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-003 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h **DĂ©pendances**: T0051 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er gestionnaire de connection pool PostgreSQL pour chat server avec configuration optimale. ### Fichiers Ă  CrĂ©er - `veza-chat-server/src/database/pool.rs` ### Fichiers Ă  Modifier - `veza-chat-server/src/main.rs` - `veza-chat-server/Cargo.toml` ### ImplĂ©mentation **Étape 1**: CrĂ©er module database/pool.rs **Étape 2**: ImplĂ©menter create_pool() avec configuration **Étape 3**: Ajouter max_connections, idle_timeout, etc. **Étape 4**: IntĂ©grer dans main.rs **Étape 5**: Tests connection pool ### Code Snippets **veza-chat-server/src/database/pool.rs**: ```rust use sqlx::{PgPool, PgPoolOptions}; use std::time::Duration; pub async fn create_pool(database_url: &str) -> Result { PgPoolOptions::new() .max_connections(20) .min_connections(5) .acquire_timeout(Duration::from_secs(30)) .idle_timeout(Duration::from_secs(600)) .max_lifetime(Duration::from_secs(1800)) .connect(database_url) .await } ``` ### Definition of Done - [x] Module `database/pool.rs` créé avec fonction `create_pool()` - [x] Configuration optimale (max_connections: 20, min_connections: 5, timeouts appropriĂ©s) - [x] Fonction `create_pool_from_env()` pour utilisation depuis variable d'environnement - [x] Module `database/mod.rs` créé pour exposer l'API - [x] IntĂ©grĂ© dans `lib.rs` (module database ajoutĂ©) - [x] IntĂ©grĂ© dans `main.rs` (initialisation du pool au dĂ©marrage) - [x] Tests unitaires créés (avec #[ignore] car nĂ©cessitent DB) - [x] Compilation rĂ©ussit (`cargo check` et `cargo build --release`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `database/pool.rs`: Fonction `create_pool()` avec configuration optimale (max 20, min 5 connexions) - `database/pool.rs`: Fonction `create_pool_from_env()` pour simplifier l'utilisation - `database/mod.rs`: Module exportant les fonctions publiques - `lib.rs`: Module `database` ajoutĂ© aux exports - `main.rs`: Initialisation du pool au dĂ©marrage avec gestion d'erreur gracieuse --- ## T0057: Add Chat Server Environment Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-004 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter configuration environnement pour chat server (DATABASE_URL, PORT, etc.) avec dotenv. ### Fichiers Ă  Modifier - `veza-chat-server/src/config.rs` - `veza-chat-server/Cargo.toml` ### ImplĂ©mentation **Étape 1**: Ajouter dotenv dependency **Étape 2**: CrĂ©er struct Config **Étape 3**: ImplĂ©menter Config::from_env() **Étape 4**: Utiliser dans main.rs ### Code Snippets **veza-chat-server/src/config.rs**: ```rust use dotenv::dotenv; use std::env; #[derive(Debug, Clone)] pub struct Config { pub database_url: String, pub port: u16, pub host: String, } impl Config { pub fn from_env() -> Result> { dotenv().ok(); Ok(Config { database_url: env::var("DATABASE_URL")?, port: env::var("CHAT_SERVER_PORT") .unwrap_or_else(|_| "8081".to_string()) .parse()?, host: env::var("CHAT_SERVER_HOST") .unwrap_or_else(|_| "0.0.0.0".to_string()), }) } } ``` ### Definition of Done - [x] Struct `Config` créée avec `database_url`, `port`, `host` - [x] `dotenvy` intĂ©grĂ© (dĂ©jĂ  prĂ©sent dans Cargo.toml) - [x] MĂ©thode `Config::from_env()` implĂ©mentĂ©e - [x] Variables d'environnement chargĂ©es (DATABASE_URL requis, CHAT_SERVER_PORT et CHAT_SERVER_HOST optionnels avec defaults) - [x] Tests unitaires créés (test_config_from_env, test_config_from_env_defaults, test_config_from_env_missing_database_url) - [x] Documentation ajoutĂ©e avec exemples d'utilisation - [x] Compilation rĂ©ussit (`cargo check`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `config.rs`: Struct `Config` ajoutĂ©e avec champs `database_url`, `port`, `host` - `config.rs`: ImplĂ©mentation `Config::from_env()` utilisant `dotenvy::dotenv()` - `config.rs`: Support des valeurs par dĂ©faut (port: 8081, host: "0.0.0.0") - `config.rs`: Tests unitaires complets avec gestion des variables d'environnement - La struct `Config` peut ĂȘtre utilisĂ©e dans `main.rs` si nĂ©cessaire pour charger la configuration depuis l'environnement --- ## T0058: Add Chat Server WebSocket Handler ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-005 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0056 ✅, T0057 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er handler WebSocket pour chat server avec Axum, gestion connexions, routing messages. ### Fichiers Ă  Modifier - `veza-chat-server/src/websocket/handler.rs` - `veza-chat-server/src/main.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er WebSocket handler avec Axum **Étape 2**: GĂ©rer connexions/dĂ©connexions **Étape 3**: Router messages (join, leave, send) **Étape 4**: IntĂ©grer dans main.rs **Étape 5**: Tests WebSocket ### Code Snippets **veza-chat-server/src/websocket/handler.rs**: ```rust use axum::extract::ws::{WebSocket, Message}; use axum::extract::WebSocketUpgrade; use std::sync::Arc; use tokio::sync::RwLock; pub async fn websocket_handler( ws: WebSocketUpgrade, ) -> axum::response::Response { ws.on_upgrade(handle_socket) } async fn handle_socket(socket: WebSocket) { // Handle WebSocket connection } ``` ### Definition of Done - [x] Module `websocket/handler.rs` créé avec handler Axum - [x] Handler `websocket_handler()` implĂ©mentĂ© avec gestion upgrade HTTP → WebSocket - [x] Fonction `handle_socket()` pour gestion connexions/dĂ©connexions individuelles - [x] Routing messages implĂ©mentĂ© (SendMessage, JoinConversation, LeaveConversation, MarkAsRead, Ping) - [x] Gestion Ping/Pong pour maintenir la connexion - [x] IntĂ©grĂ© dans `main.rs` avec route `/ws` - [x] Structure `WebSocketState` pour partager l'Ă©tat entre handlers - [x] Gestion d'erreurs avec messages d'erreur JSON au client - [x] Module `websocket/mod.rs` restructurĂ© pour exposer handler et types - [x] Compilation rĂ©ussit (`cargo check`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `websocket/handler.rs`: Handler complet avec gestion connexions, dĂ©connexions, routage messages - `websocket/mod.rs`: Restructuration du module pour exposer types et handler - `main.rs`: IntĂ©gration du handler avec route `/ws` et Ă©tat partagĂ© - Support complet des messages: SendMessage, JoinConversation, LeaveConversation, MarkAsRead, Ping/Pong - Messages de bienvenue et confirmations d'actions - Gestion d'erreurs robuste avec messages JSON structurĂ©s --- ## T0059: Add Chat Server Message Broadcasting ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-006 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0058 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter systĂšme de broadcast messages Ă  tous clients dans une room. ### Fichiers Ă  Modifier - `veza-chat-server/src/websocket/broadcast.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er struct BroadcastManager **Étape 2**: GĂ©rer subscriptions par room **Étape 3**: ImplĂ©menter broadcast_to_room() **Étape 4**: GĂ©rer dĂ©sinscriptions **Étape 5**: Tests broadcasting ### Code Snippets **veza-chat-server/src/websocket/broadcast.rs**: ```rust use std::collections::HashMap; use uuid::Uuid; use tokio::sync::broadcast; pub struct BroadcastManager { rooms: HashMap>, } impl BroadcastManager { pub fn broadcast_to_room(&self, room_id: Uuid, message: String) { // Broadcast implementation } } ``` ### Definition of Done - [x] `BroadcastManager` créé avec structure utilisant `tokio::sync::broadcast` - [x] Gestion des subscriptions par room avec `subscribe_to_room()` - [x] `broadcast_to_room()` implĂ©mentĂ© pour diffuser des messages - [x] Gestion automatique des dĂ©sinscriptions (cleanup des rooms vides) - [x] MĂ©thodes utilitaires (`subscriber_count()`, `active_rooms()`, `cleanup_empty_room()`) - [x] Tests unitaires complets (crĂ©ation, subscription, broadcast, multiples subscribers, cleanup) - [x] Module `broadcast.rs` intĂ©grĂ© dans `websocket/mod.rs` - [x] Export du `BroadcastManager` depuis le module websocket - [x] Compilation rĂ©ussit (`cargo check`) - [x] Tests passent avec succĂšs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `websocket/broadcast.rs`: `BroadcastManager` utilisant `tokio::sync::broadcast::Sender` par room - Gestion automatique des canaux de broadcast (crĂ©ation Ă  la premiĂšre subscription) - SĂ©rialisation automatique des `OutgoingMessage` en JSON avant broadcast - Nettoyage automatique des rooms vides pour libĂ©rer la mĂ©moire - Support de multiples subscribers par room avec broadcast efficace - Tests unitaires complets couvrant tous les cas d'usage --- ## T0060: Add Chat Server Room Management ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-007 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0054 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter gestion rooms (crĂ©ation, suppression, liste utilisateurs) avec repository. ### Fichiers Ă  Modifier - `veza-chat-server/src/repository/room_repository.rs` - `veza-chat-server/src/services/room_service.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er RoomService **Étape 2**: ImplĂ©menter create_room(), delete_room() **Étape 3**: ImplĂ©menter add_user(), remove_user() **Étape 4**: ImplĂ©menter list_users() **Étape 5**: Tests room management ### Code Snippets **veza-chat-server/src/services/room_service.rs**: ```rust use uuid::Uuid; use crate::repository::RoomRepository; pub struct RoomService { repo: RoomRepository, } impl RoomService { pub async fn create_room(&self, name: &str) -> Result { // Create room } pub async fn add_user(&self, room_id: Uuid, user_id: Uuid) -> Result<()> { // Add user to room } } ``` ### Definition of Done - [x] Module `services/room_service.rs` créé avec `RoomService` - [x] `create_room()` implĂ©mentĂ© avec ajout automatique du crĂ©ateur comme owner - [x] `delete_room()` implĂ©mentĂ© avec suppression des membres et de la room - [x] `add_user()` implĂ©mentĂ© avec validation de l'existence de la room - [x] `remove_user()` implĂ©mentĂ© avec validation de l'existence de la room - [x] `list_users()` implĂ©mentĂ© pour rĂ©cupĂ©rer tous les membres d'une room - [x] `get_room()` implĂ©mentĂ© pour rĂ©cupĂ©rer une room par ID - [x] Gestion d'erreurs complĂšte avec `ChatError` (not_found, internal_error) - [x] Module `services/mod.rs` créé et intĂ©grĂ© dans `lib.rs` - [x] Exports de `Room` et `RoomMember` ajoutĂ©s dans `repository/mod.rs` - [x] Tests unitaires ajoutĂ©s (avec #[ignore] car nĂ©cessitent DB) - [x] Logging avec `tracing` (info, debug, warn) - [x] Compilation rĂ©ussit (`cargo check`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `services/room_service.rs`: Service de haut niveau encapsulant la logique mĂ©tier - Utilise `RoomRepository` pour toutes les opĂ©rations de base de donnĂ©es - Validation systĂ©matique de l'existence des rooms avant les opĂ©rations - Ajout automatique du crĂ©ateur comme membre "owner" lors de la crĂ©ation - Suppression en cascade des membres lors de la suppression d'une room - Gestion d'erreurs robuste avec messages d'erreur clairs - Documentation complĂšte avec exemples d'utilisation --- ## T0061: Add Chat Server Error Handling ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-008 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er systĂšme erreurs structurĂ© pour chat server avec types d'erreurs. ### Fichiers Ă  Modifier - `veza-chat-server/src/error.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er enum ChatError **Étape 2**: ImplĂ©menter Display, Error traits **Étape 3**: Ajouter conversions depuis sqlx::Error, etc. **Étape 4**: CrĂ©er Result alias **Étape 5**: Tests error handling ### Code Snippets **veza-chat-server/src/error.rs**: ```rust #[derive(Debug)] pub enum ChatError { Database(sqlx::Error), NotFound(String), Unauthorized, InvalidMessage, } impl std::fmt::Display for ChatError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { // Display implementation } } impl std::error::Error for ChatError {} ``` ### Definition of Done - [x] Enum `ChatError` créé avec de nombreux variants couvrant tous les cas d'usage - [x] Traits `Display` et `Error` implĂ©mentĂ©s via `thiserror::Error` (automatique) - [x] Conversions depuis erreurs externes : `From`, `From`, `From`, `From` - [x] Alias `Result` créé : `pub type Result = std::result::Result` - [x] Tests error handling créés (5 tests unitaires couvrant http_status, severity, public_message, helpers, macro) - [x] MĂ©thodes utilitaires : `http_status()`, `severity()`, `public_message()`, helpers pour crĂ©er des erreurs - [x] Macro `chat_error!` pour simplifier la crĂ©ation d'erreurs - [x] Enum `ErrorSeverity` pour catĂ©goriser la gravitĂ© des erreurs - [x] Compilation rĂ©ussit (`cargo check`) - [x] Tests passent avec succĂšs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - `error.rs` : SystĂšme d'erreurs complet avec ~30+ variants couvrant : - Authentification et autorisation (InvalidToken, Unauthorized, InvalidCredentials, etc.) - Validation et contenu (MessageTooLong, InvalidFormat, SpamDetected, etc.) - Rate limiting et quota (RateLimitExceeded, QuotaExceeded, etc.) - RĂ©seau et WebSocket (WebSocket, ConnectionClosed, etc.) - Base de donnĂ©es (Database, NotFound, Conflict, etc.) - Conversations et messages (ConversationNotFound, MessageNotFound, etc.) - Fichiers et upload (FileTooLarge, UnsupportedFileType, etc.) - SystĂšme et configuration (Configuration, ServiceUnavailable, etc.) - Permissions et rĂ©actions (PermissionDenied, ReactionAlreadyExists, etc.) - SĂ©curitĂ© (SuspiciousActivity, IpBlocked, InjectionAttempt, etc.) - Utilisation de `thiserror::Error` pour implĂ©menter automatiquement `Display` et `Error` - Conversions automatiques depuis les erreurs externes courantes - MĂ©thodes helper pour crĂ©er des erreurs avec contexte (database_error, not_found, unauthorized, etc.) - Mapping HTTP status codes appropriĂ©s pour chaque type d'erreur - SystĂšme de sĂ©vĂ©ritĂ© pour le logging (Info, Low, Medium, High, Critical, Warning) - Messages d'erreur publics sĂ©curisĂ©s pour Ă©viter la divulgation d'informations sensibles --- ## T0062: Add Chat Server Logging with Tracing ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-009 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer logging structurĂ© avec tracing pour chat server. ### Fichiers Ă  Modifier - `veza-chat-server/src/main.rs` - `veza-chat-server/Cargo.toml` ### ImplĂ©mentation **Étape 1**: Ajouter tracing, tracing-subscriber dependencies **Étape 2**: Initialiser subscriber dans main() **Étape 3**: Configurer log level depuis env **Étape 4**: Ajouter spans dans handlers ### Code Snippets **veza-chat-server/src/main.rs**: ```rust use tracing_subscriber; #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) .init(); tracing::info!("Starting chat server..."); } ``` ### Definition of Done - [x] Tracing configurĂ© avec `tracing-subscriber` (dĂ©jĂ  prĂ©sent dans Cargo.toml) - [x] Logging structurĂ© activĂ© avec `tracing_subscriber::fmt()` - [x] Log level configurable via variable d'environnement `RUST_LOG` (avec fallback Ă  "info") - [x] Utilisation de `EnvFilter` pour le filtrage par environnement - [x] Spans ajoutĂ©s dans handlers avec `#[tracing::instrument]` sur `health_check()`, `get_messages()`, `send_message()`, `get_stats()` - [x] Configuration avec `with_target(true)`, `with_file(true)`, `with_line_number(true)` pour logs dĂ©taillĂ©s - [x] Compilation rĂ©ussit (`cargo check`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **Cargo.toml** : DĂ©pendances dĂ©jĂ  prĂ©sentes : - `tracing = "0.1"` - `tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json", "ansi", "chrono"] }` - **main.rs** : - Initialisation du subscriber avec `EnvFilter::try_from_default_env()` pour lire `RUST_LOG` - Fallback Ă  "info" si la variable d'environnement n'est pas dĂ©finie - Ajout de mĂ©tadonnĂ©es (target, file, line_number) pour logs structurĂ©s - Ajout de `#[tracing::instrument]` sur tous les handlers HTTP pour crĂ©er automatiquement des spans - Les spans incluent automatiquement les paramĂštres des fonctions (sauf ceux marquĂ©s avec `skip`) - **Utilisation** : Le niveau de log peut ĂȘtre configurĂ© via `RUST_LOG=debug` ou `RUST_LOG=chat_server=debug,info` --- ## T0063: Add Chat Server Health Check Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-010 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 20min **DĂ©pendances**: T0057 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter endpoint `/health` pour health check du chat server. ### Fichiers Ă  Modifier - `veza-chat-server/src/main.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er route GET /health **Étape 2**: Retourner status 200 OK **Étape 3**: Optionnel: vĂ©rifier DB connection ### Code Snippets **veza-chat-server/src/main.rs**: ```rust use axum::{routing::get, Router}; let app = Router::new() .route("/health", get(health_check)); async fn health_check() -> &'static str { "OK" } ``` ### Definition of Done - [x] Route `/health` créée et configurĂ©e (dĂ©jĂ  prĂ©sente) - [x] Retourne status 200 OK via `ApiResponse>` - [x] VĂ©rification de la connexion DB implĂ©mentĂ©e (optionnelle) - [x] Endpoint retourne des informations de santĂ© (status, service, version, websocket, database) - [x] IntĂ©gration dans `AppState` pour accĂ©der au pool de connexions - [x] Span tracing ajoutĂ© avec `#[tracing::instrument]` - [x] Gestion des cas oĂč la DB n'est pas configurĂ©e ou indisponible - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **Route** : GET `/health` dĂ©jĂ  prĂ©sente dans le Router - **Handler** : `health_check()` amĂ©liorĂ© pour : - Retourner un JSON structurĂ© avec `ApiResponse>` - Inclure des informations de base : status, service, version, websocket - VĂ©rifier la connexion Ă  la base de donnĂ©es si le pool est disponible - GĂ©rer les cas oĂč la DB n'est pas configurĂ©e (`database_pool` est `None`) - Retourner "connected", "error: ..." ou "not_configured" selon l'Ă©tat de la DB - **AppState** : Ajout de `database_pool: Option` pour permettre la vĂ©rification de la DB - **VĂ©rification DB** : Utilise `sqlx::query("SELECT 1").execute(pool).await` pour tester la connexion - **Tracing** : Span automatique avec `#[tracing::instrument]` pour le monitoring --- ## T0064: Add Chat Server Integration Tests ✅ COMPLÉTÉE **Feature Parente**: FEAT-CHAT-011 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0058 ✅, T0059 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er tests intĂ©gration pour chat server (WebSocket, rooms, messages). ### Fichiers Ă  CrĂ©er - `veza-chat-server/tests/integration_test.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er setup test DB **Étape 2**: Tests WebSocket connexion **Étape 3**: Tests message sending/receiving **Étape 4**: Tests room management **Étape 5**: Tests broadcasting ### Code Snippets **veza-chat-server/tests/integration_test.rs**: ```rust #[tokio::test] async fn test_websocket_connection() { // Test WebSocket connection } #[tokio::test] async fn test_send_message() { // Test sending message } ``` ### Definition of Done - [x] Tests d'intĂ©gration créés dans `tests/integration_test.rs` - [x] Setup de la base de donnĂ©es de test avec `setup_test_db()` et support de `TEST_DATABASE_URL` - [x] Tests WebSocket : `test_websocket_connection()` et `test_send_message()` (marquĂ©s `#[ignore]` car nĂ©cessitent serveur) - [x] Tests room management : `test_room_management()` avec crĂ©ation, ajout/utilisateur, liste, retrait, suppression - [x] Tests broadcasting : `test_broadcasting()` avec subscription, broadcast, rĂ©ception, cleanup - [x] Tests message store : `test_message_store()` pour envoi et rĂ©cupĂ©ration de messages - [x] Test d'intĂ©gration complet : `test_integration_complete()` combinant WebSocket + Messages + Rooms - [x] Utilisation de `tokio-tungstenite` pour les tests WebSocket client - [x] Gestion des cas oĂč la DB n'est pas disponible (tests ignorĂ©s gracieusement) - [x] Tests compilent avec succĂšs (`cargo check --test integration_test`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **tests/integration_test.rs** : Suite complĂšte de tests d'intĂ©gration avec : - `setup_test_db()` : Configuration de la base de donnĂ©es de test via `TEST_DATABASE_URL` (fallback Ă  `postgresql://veza:password@localhost:5432/veza_test`) - `test_websocket_connection()` : Test de connexion WebSocket au serveur (nĂ©cessite serveur en cours d'exĂ©cution) - `test_send_message()` : Test d'envoi et rĂ©ception de messages via WebSocket - `test_room_management()` : Tests complets du `RoomService` (crĂ©ation, ajout utilisateur, liste, retrait, suppression) - `test_broadcasting()` : Tests du `BroadcastManager` avec subscription, broadcast, rĂ©ception par multiple receivers - `test_message_store()` : Tests du `SimpleMessageStore` pour envoi et rĂ©cupĂ©ration - `test_integration_complete()` : Test d'intĂ©gration complĂšte combinant tous les composants - **Marquage `#[ignore]`** : Tests nĂ©cessitant un serveur en cours d'exĂ©cution sont marquĂ©s pour ĂȘtre exĂ©cutĂ©s manuellement - **Gestion des erreurs** : Tests gĂšrent gracieusement l'absence de base de donnĂ©es ou de serveur - **Utilisation de `tokio-tungstenite`** : Pour les tests WebSocket client (dĂ©jĂ  prĂ©sent dans les dĂ©pendances) - **Assertions complĂštes** : VĂ©rification de tous les aspects fonctionnels (crĂ©ation, rĂ©cupĂ©ration, suppression, broadcasting) --- ## T0065: Fix Stream Server Missing Imports ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-001 **Phase**: 1 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter imports manquants dans stream server (HashMap, trace dans structured_logging.rs). ### Fichiers Ă  Modifier - `veza-stream-server/src/structured_logging.rs` - Autres fichiers avec erreurs imports ### ImplĂ©mentation **Étape 1**: Identifier imports manquants via cargo check **Étape 2**: Ajouter imports nĂ©cessaires **Étape 3**: VĂ©rifier compilation ### Code Snippets **veza-stream-server/src/structured_logging.rs**: ```rust use std::collections::HashMap; // ✅ Ajouter use tracing::{trace, debug, info, warn, error}; // ✅ Ajouter si nĂ©cessaire ``` ### Definition of Done - [x] `HashMap` et `trace` vĂ©rifiĂ©s - dĂ©jĂ  prĂ©sents dans les imports (lignes 13 et 16) - [x] Types manquants créés : `LoggingConfig` et `LogRotation` dĂ©finis dans `structured_logging.rs` - [x] Import `Config` corrigĂ© : utilisation de `crate::config::Config` au lieu de `ServerConfig` - [x] `AppError::ConfigError` corrigĂ© : remplacement de `AppError::Configuration` par `AppError::ConfigError` - [x] `appender` corrigĂ© : ajoutĂ© dans la structure `StructuredLogging` et utilisĂ© via `self.appender` - [x] `Rotation::Daily` et `Rotation::Hourly` corrigĂ©s : utilisation des variants de l'enum au lieu de mĂ©thodes - [x] `init_logging_from_config` adaptĂ© : utilisation de `Config` au lieu de `ServerConfig` - [x] Compilation rĂ©ussit pour `structured_logging.rs` (`cargo check --lib`) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **structured_logging.rs** : - `HashMap` et `trace` Ă©taient dĂ©jĂ  prĂ©sents dans les imports (lignes 13 et 16) - CrĂ©ation de `LoggingConfig` et `LogRotation` structs dans le fichier - Correction de l'import `Config` : utilisation de `crate::config::Config` - Correction de `appender` : ajoutĂ© dans la structure et utilisĂ© via `self.appender` dans `setup()` - Correction de `Rotation::daily()` et `Rotation::hourly()` : utilisation de `Rotation::Daily` et `Rotation::Hourly` (variants de l'enum) - Correction de `AppError::Configuration` : remplacĂ© par `AppError::ConfigError` - Adaptation de `init_logging_from_config` : utilise `Config` et extrait les valeurs depuis `config.monitoring` --- ## T0066: Fix Stream Server WebRTC Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0065 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer WebRTC pour stream server (ICE servers, signaling). ### Fichiers Ă  Modifier - `veza-stream-server/src/streaming/webrtc/config.rs` (créé) - `veza-stream-server/src/streaming/webrtc.rs` (modifiĂ©) - `veza-stream-server/src/main.rs` (modifiĂ©) ### ImplĂ©mentation **Étape 1**: CrĂ©er WebRTC config struct **Étape 2**: Configurer ICE servers **Étape 3**: Configurer signaling **Étape 4**: IntĂ©grer dans main ### Code Snippets **veza-stream-server/src/streaming/webrtc/config.rs**: ```rust pub struct WebRTCConfig { pub ice_servers: Vec, pub signaling_url: String, } ``` ### Definition of Done - [x] Module `webrtc/config.rs` créé avec `WebRTCConfig` struct - [x] ICE servers configurĂ©s avec support STUN/TURN via variables d'environnement - [x] Signaling URL configurĂ© avec support WebSocket (ws:// ou wss://) - [x] Configuration depuis variables d'environnement : `WebRTCConfig::from_env()` - [x] Support parsing JSON et CSV pour serveurs ICE - [x] Validation de la configuration : `validate()` method - [x] IntĂ©gration dans `main.rs` avec initialisation et logging - [x] Tests unitaires créés pour configuration, parsing, et validation - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **webrtc/config.rs** : - Structure `WebRTCConfig` avec `ice_servers`, `signaling_url`, `max_peers`, `connection_timeout`, `heartbeat_interval`, `codec_preferences`, `bitrate_adaptation`, `jitter_buffer_ms` - `from_env()` : Charge la configuration depuis variables d'environnement : - `WEBRTC_ICE_SERVERS` : JSON ou CSV des serveurs ICE - `WEBRTC_STUN_URL` : URL serveur STUN personnalisĂ© - `WEBRTC_TURN_URL`, `WEBRTC_TURN_USERNAME`, `WEBRTC_TURN_CREDENTIAL` : Configuration TURN - `WEBRTC_SIGNALING_URL` : URL de signaling WebSocket - `WEBRTC_MAX_PEERS` : Nombre maximum de peers - `WEBRTC_CONNECTION_TIMEOUT` : Timeout de connexion en secondes - `WEBRTC_HEARTBEAT_INTERVAL` : Intervalle de heartbeat en secondes - `WEBRTC_BITRATE_ADAPTATION` : Activation adaptation de bitrate - `WEBRTC_JITTER_BUFFER_MS` : Taille du jitter buffer en millisecondes - `parse_ice_servers()` : Parse JSON ou CSV pour les serveurs ICE - `validate()` : Valide la configuration (serveurs ICE, URL signaling, etc.) - Tests unitaires pour default config, parsing JSON/CSV, validation - **webrtc.rs** : - DĂ©placement de `WebRTCConfig` vers `config.rs` - RĂ©-export de `WebRTCConfig` depuis `config` module - Types `IceServer` et `AudioCodec` conservĂ©s dans `webrtc.rs` pour compatibilitĂ© - **main.rs** : - Initialisation de `WebRTCConfig::from_env()` dans `create_app_state()` - Logging de la configuration WebRTC (nombre de serveurs ICE, URL signaling) - Validation de la configuration avec warning si invalide --- ## T0067: Add Stream Server Audio Pipeline ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-003 **Phase**: 1 **Priority**: high **Complexity**: high **Temps EstimĂ©**: 3h **DĂ©pendances**: T0066 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er pipeline audio pour streaming (dĂ©codage, traitement, encodage). ### Fichiers Ă  CrĂ©er - `veza-stream-server/src/audio/pipeline.rs` (créé) - `veza-stream-server/src/lib.rs` (modifiĂ© pour exposer codecs) ### ImplĂ©mentation **Étape 1**: CrĂ©er AudioPipeline struct **Étape 2**: ImplĂ©menter dĂ©codage audio **Étape 3**: ImplĂ©menter traitement (volume, EQ) **Étape 4**: ImplĂ©menter encodage **Étape 5**: Tests pipeline ### Code Snippets **veza-stream-server/src/audio/pipeline.rs**: ```rust pub struct AudioPipeline { decoder: AudioDecoder, processor: AudioProcessor, encoder: AudioEncoder, } impl AudioPipeline { pub async fn process(&mut self, input: &[u8]) -> Result> { // Process audio } } ``` ### Definition of Done - [x] `AudioPipeline` struct créé avec decoder, processor, encoder - [x] DĂ©codage audio implĂ©mentĂ© : utilisation de `AudioDecoder` trait pour dĂ©coder les bytes en Ă©chantillons - [x] Traitement audio implĂ©mentĂ© : `AudioPipelineProcessor` avec : - Ajustement du volume (0.0 Ă  1.0) - Application de chaĂźne d'effets (`EffectsChain`) - Normalisation optionnelle pour Ă©viter le clipping - [x] Encodage audio implĂ©mentĂ© : utilisation de `AudioEncoder` trait pour encoder les Ă©chantillons en bytes - [x] MĂ©thode `process()` : Traite un buffer audio encodĂ© (dĂ©code → traite → encode) - [x] MĂ©thode `process_stream()` : Traite un stream audio complet avec plusieurs chunks - [x] MĂ©thodes de configuration : `set_volume()`, `set_effects_chain()`, `set_normalize()` - [x] MĂ©thode `reset()` : RĂ©initialise le dĂ©codeur et l'encodeur - [x] Tests pipeline créés : 6 tests unitaires couvrant crĂ©ation, process, volume, normalisation, reset, empty input - [x] Module `codecs` exposĂ© dans `lib.rs` pour accĂšs aux traits `AudioDecoder` et `AudioEncoder` - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **audio/pipeline.rs** : - Structure `AudioPipeline` avec `decoder: Box`, `processor: AudioPipelineProcessor`, `encoder: Box` - Structure interne `AudioPipelineProcessor` pour gĂ©rer le volume, les effets et la normalisation - MĂ©thode `process()` : DĂ©code l'input → traite les Ă©chantillons → encode la sortie - MĂ©thode `process_stream()` : Traite plusieurs chunks et concatĂšne les rĂ©sultats - Support de `EffectsChain` pour appliquer des effets audio complexes - Normalisation automatique pour Ă©viter le clipping (gain reduction si pic > 0.95) - Tests avec mocks `MockDecoder` et `MockEncoder` pour validation - **lib.rs** : - Ajout de `pub mod codecs;` pour exposer le module codecs - **audio/mod.rs** : - Ajout de `pub mod pipeline;` et `pub use pipeline::*;` pour exposer le pipeline --- ## T0068: Add Stream Server Connection Pool ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-004 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h **DĂ©pendances**: T0065 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er connection pool PostgreSQL pour stream server. ### Fichiers Ă  CrĂ©er - `veza-stream-server/src/database/pool.rs` (créé) - `veza-stream-server/src/database/mod.rs` (créé) - `veza-stream-server/src/lib.rs` (modifiĂ©) - `veza-stream-server/src/main.rs` (modifiĂ©) ### ImplĂ©mentation **Étape 1**: CrĂ©er pool.rs avec create_pool() **Étape 2**: Configurer max_connections, timeouts **Étape 3**: IntĂ©grer dans main.rs **Étape 4**: Tests pool ### Code Snippets **veza-stream-server/src/database/pool.rs**: ```rust use sqlx::{PgPool, PgPoolOptions}; pub async fn create_pool(database_url: &str) -> Result { PgPoolOptions::new() .max_connections(10) .connect(database_url) .await } ``` ### Definition of Done - [x] Module `database/pool.rs` créé avec `create_pool()` et `create_pool_from_config()` - [x] Configuration optimale : Utilise `DatabaseConfig` pour max_connections, min_connections, timeouts - [x] Support de plusieurs fonctions : `create_pool()`, `create_pool_from_config()`, `create_pool_from_env()` - [x] Configuration des timeouts : `acquire_timeout`, `idle_timeout`, `max_lifetime` depuis `DatabaseConfig` - [x] IntĂ©grĂ© dans `main.rs` : CrĂ©ation du pool dans `create_app_state()` avec gestion d'erreur gracieuse - [x] Module `database/mod.rs` créé pour exposer le module pool - [x] Module `database` exposĂ© dans `lib.rs` - [x] Tests pool créés : 3 tests (create_pool, create_pool_from_env, create_pool_from_config_structure) - [x] Tests marquĂ©s `#[ignore]` car nĂ©cessitent une base de donnĂ©es de test - [x] Logging intĂ©grĂ© : Info et debug logs pour le suivi de la crĂ©ation du pool - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **database/pool.rs** : - `create_pool(database_url)` : CrĂ©e un pool avec configuration par dĂ©faut (max=10, min=1, timeout=30s, idle=600s, lifetime=3600s) - `create_pool_from_config(config)` : CrĂ©e un pool depuis `DatabaseConfig` avec tous les paramĂštres configurables - `create_pool_from_env(env_var)` : CrĂ©e un pool depuis une variable d'environnement - Utilise `PgPoolOptions` de `sqlx` pour la configuration - Logging avec `tracing` pour info et debug - Tests unitaires pour validation - **database/mod.rs** : - Module d'exposition pour le pool - RĂ©-export de toutes les fonctions publiques - **lib.rs** : - Ajout de `pub mod database;` pour exposer le module - **main.rs** : - CrĂ©ation du pool dans `create_app_state()` via `create_pool_from_config(&config.database)` - Gestion d'erreur gracieuse : `Option` si la crĂ©ation Ă©choue (warning log) - Logging de succĂšs ou d'Ă©chec --- ## T0069: Add Stream Server Environment Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-005 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter configuration environnement pour stream server. ### Fichiers Ă  Modifier - `veza-stream-server/src/config/mod.rs` (modifiĂ©) - `veza-stream-server/Cargo.toml` (dĂ©jĂ  contient dotenv) ### ImplĂ©mentation **Étape 1**: Ajouter dotenv **Étape 2**: CrĂ©er struct Config **Étape 3**: ImplĂ©menter from_env() **Étape 4**: Utiliser dans main ### Code Snippets **veza-stream-server/src/config/mod.rs**: ```rust use dotenv::dotenv; use std::env; #[derive(Debug, Clone)] pub struct Config { pub database_url: String, pub port: u16, } impl Config { pub fn from_env() -> Result> { dotenv().ok(); Ok(Config { database_url: env::var("DATABASE_URL")?, port: env::var("STREAM_SERVER_PORT") .unwrap_or_else(|_| "8082".to_string()) .parse()?, }) } } ``` ### Definition of Done - [x] `Config` struct existe dĂ©jĂ  (structure complĂšte avec toutes les configurations) - [x] `dotenv` intĂ©grĂ© : Ajout de `use dotenv::dotenv;` et appel de `dotenv().ok();` dans `from_env()` - [x] Variables environnement chargĂ©es : `dotenv().ok();` appelĂ© au dĂ©but de `from_env()` pour charger `.env` si disponible - [x] `from_env()` implĂ©mentĂ© : La fonction existait dĂ©jĂ  et charge maintenant les variables depuis `.env` - [x] Utilisation dans `main.rs` : `Config::from_env()` est dĂ©jĂ  utilisĂ© dans `main.rs` (ligne 38) - [x] Tests config créés : 3 tests ajoutĂ©s dans le module de tests : - `test_config_from_env()` : Test de crĂ©ation de config depuis variables d'environnement - `test_dotenv_loads()` : Test que dotenv() peut ĂȘtre appelĂ© sans erreur - `test_config_default()` : Test que Config::default() fonctionne - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **config/mod.rs** : - Ajout de `use dotenv::dotenv;` dans les imports - Ajout de `dotenv().ok();` au dĂ©but de `from_env()` pour charger le fichier `.env` si disponible - Le `.ok()` permet de continuer mĂȘme si le fichier `.env` n'existe pas (pas d'erreur fatale) - Tests unitaires ajoutĂ©s pour valider l'intĂ©gration de dotenv et la crĂ©ation de config - **Cargo.toml** : - `dotenv = "0.15"` Ă©tait dĂ©jĂ  prĂ©sent dans les dĂ©pendances - **main.rs** : - `Config::from_env()` Ă©tait dĂ©jĂ  utilisĂ© (ligne 38) - La configuration charge maintenant automatiquement les variables depuis `.env` grĂące Ă  `dotenv().ok()` --- ## T0070: Add Frontend Vite Build Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-001 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer Vite build pour frontend React avec optimisations production. ### Fichiers Ă  Modifier - `apps/web/vite.config.ts` (modifiĂ©) - `apps/web/package.json` (dĂ©jĂ  configurĂ©) ### ImplĂ©mentation **Étape 1**: VĂ©rifier vite.config.ts existe **Étape 2**: Configurer build optimizations **Étape 3**: Configurer source maps **Étape 4**: Configurer chunk splitting ### Code Snippets **apps/web/vite.config.ts**: ```typescript import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], build: { sourcemap: true, rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], }, }, }, }, }); ``` ### Definition of Done - [x] Vite config optimisĂ© : Configuration complĂšte ajoutĂ©e avec build optimizations - [x] Build production fonctionne : `npm run build` fonctionne correctement - [x] Source maps configurĂ©s : `sourcemap: true` activĂ© pour le debugging en production - [x] Chunk splitting configurĂ© : `manualChunks` configurĂ© avec : - `vendor`: React et React DOM - `router`: react-router-dom - `ui-libs`: Toutes les bibliothĂšques Radix UI - `state-libs`: Zustand et TanStack Query - `utils`: Utilitaires (axios, zod, clsx, tailwind-merge) - [x] Optimisations supplĂ©mentaires : - Minification avec esbuild - Target ES2020+ pour meilleures performances - Organisation des assets (CSS, images, fonts) dans des dossiers sĂ©parĂ©s - Inline des petits assets (< 4KB) pour rĂ©duire les requĂȘtes HTTP - Noms de fichiers avec hash pour cache busting - Warning limit pour les chunks trop gros (1000KB) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **vite.config.ts** : - Configuration `build` complĂšte ajoutĂ©e avec toutes les optimisations - `sourcemap: true` pour le debugging en production - `minify: 'esbuild'` pour une minification rapide - `target: 'esnext'` pour utiliser les derniĂšres fonctionnalitĂ©s JS - `manualChunks` pour sĂ©parer le code en chunks optimisĂ©s : - Vendor chunk (React, React DOM) - Router chunk (react-router-dom) - UI libraries chunk (toutes les libs Radix UI) - State management chunk (Zustand, TanStack Query) - Utils chunk (axios, zod, clsx, tailwind-merge) - Organisation des assets : CSS, images, fonts dans des dossiers sĂ©parĂ©s - Hash dans les noms de fichiers pour cache busting - `assetsInlineLimit: 4096` pour inline les petits assets - `chunkSizeWarningLimit: 1000` pour avertir sur les chunks trop gros - **package.json** : - Script `build` dĂ©jĂ  prĂ©sent : `"build": "tsc -b && vite build"` - Pas de modifications nĂ©cessaires --- ## T0071: Add Frontend Path Aliases Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-002 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0070 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Configurer path aliases (@ pour src/) dans Vite et TypeScript. ### Fichiers Ă  Modifier - `apps/web/vite.config.ts` (dĂ©jĂ  configurĂ©) - `apps/web/tsconfig.app.json` (dĂ©jĂ  configurĂ©) ### ImplĂ©mentation **Étape 1**: Ajouter resolve.alias dans vite.config.ts **Étape 2**: Ajouter paths dans tsconfig.json **Étape 3**: VĂ©rifier imports fonctionnent ### Code Snippets **apps/web/vite.config.ts**: ```typescript import path from 'path'; export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, './src'), }, }, }); ``` **apps/web/tsconfig.app.json**: ```json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` ### Definition of Done - [x] Path aliases configurĂ©s dans Vite : `@` et plusieurs autres aliases configurĂ©s dans `vite.config.ts` (lignes 48-56) - [x] Path aliases configurĂ©s dans TypeScript : `paths` configurĂ©s dans `tsconfig.app.json` (lignes 28-35) avec `baseUrl: "."` - [x] Imports `@/` fonctionnent : Les imports avec `@/` sont utilisĂ©s dans le codebase - [x] Aliases supplĂ©mentaires configurĂ©s : - `@components/*` → `./src/components/*` - `@features/*` → `./src/features/*` - `@services/*` → `./src/services/*` - `@hooks/*` → `./src/hooks/*` - `@utils/*` → `./src/utils/*` - `@types/*` → `./src/types/*` - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **vite.config.ts** : - Path aliases dĂ©jĂ  configurĂ©s dans `resolve.alias` (lignes 48-56) - `@` alias pointant vers `./src` - Plusieurs autres aliases pour une meilleure organisation (components, features, services, hooks, utils, types) - **tsconfig.app.json** : - `baseUrl: "."` configurĂ© (ligne 27) - `paths` configurĂ© avec tous les aliases (lignes 28-35) - `@/*` mappĂ© vers `./src/*` - Tous les autres aliases Ă©galement configurĂ©s pour correspondre Ă  Vite - **Utilisation** : Les imports avec `@/` sont utilisĂ©s dans le codebase, confirmant que la configuration fonctionne correctement --- ## T0072: Create Frontend Services API Client ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-003 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0071 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er client API centralisĂ© pour appels HTTP avec interceptors, error handling. ### Fichiers Ă  CrĂ©er - `apps/web/src/services/api.ts` (dĂ©jĂ  créé et complet) ### ImplĂ©mentation **Étape 1**: CrĂ©er api.ts avec axios/fetch **Étape 2**: Configurer base URL **Étape 3**: Ajouter interceptors (auth, errors) **Étape 4**: CrĂ©er mĂ©thodes helpers (get, post, etc.) ### Code Snippets **apps/web/src/services/api.ts**: ```typescript import axios from 'axios'; const api = axios.create({ baseURL: import.meta.env.VITE_API_URL || 'http://localhost:8080/api', }); api.interceptors.request.use((config) => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); export default api; ``` ### Definition of Done - [x] Client API créé : `ApiService` class avec instance singleton `apiService` - [x] Base URL configurĂ©e : `API_BASE_URL` depuis `VITE_API_BASE_URL` ou valeur par dĂ©faut - [x] Interceptors ajoutĂ©s : - Request interceptor : Ajoute le token Bearer dans les headers - Response interceptor : GĂšre les erreurs 401 avec refresh token automatique - Error handling : Conversion des erreurs en format `ApiError` standardisĂ© - [x] Helpers mĂ©thodes créés : MĂ©thodes complĂštes pour : - Authentification : `login()`, `register()`, `logout()`, `getCurrentUser()` - Utilisateurs : `getUsers()`, `getUser()`, `updateUser()` - Tracks : `getTracks()`, `getTrack()`, `uploadTrack()` - BibliothĂšque : `getLibraryItems()`, `uploadFile()`, `toggleFavorite()` - Messages : `getMessages()`, `sendMessage()` - Conversations : `getConversations()`, `createConversation()` - Utilitaires : `getWebSocketUrl()`, `isAuthenticated()` - [x] Gestion avancĂ©e des tokens : - Access token et refresh token dans localStorage - Refresh automatique du token en cas d'expiration - Gestion des requĂȘtes concurrentes avec `refreshPromise` - Redirection vers `/login` si refresh Ă©choue - [x] Validation des donnĂ©es : Utilisation de Zod pour valider les rĂ©ponses API - [x] Tests API client créés : `apps/web/src/test/api.test.ts` avec tests pour le service API - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **api.ts** : - Classe `ApiService` avec instance Axios configurĂ©e (baseURL, timeout, headers) - `setupInterceptors()` : Configuration des interceptors request et response - Request interceptor : Ajoute le token Bearer depuis localStorage - Response interceptor : GĂšre les erreurs 401 avec refresh automatique du token - `refreshAccessToken()` : MĂ©thode privĂ©e pour rafraĂźchir le token avec gestion des requĂȘtes concurrentes - `handleError()` : Conversion des erreurs Axios en format `ApiError` standardisĂ© - Validation Zod : SchĂ©mas pour `User`, `AuthTokens`, `ApiError` - MĂ©thodes complĂštes pour toutes les ressources (auth, users, tracks, library, messages, conversations) - Singleton instance : `export const apiService = new ApiService()` - Support FormData pour les uploads de fichiers - Configuration WebSocket URL avec token - **api.test.ts** : - Tests unitaires pour le service API - Tests d'intĂ©gration avec mocks --- ## T0073: Add Stream Server WebSocket Handler ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-006 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0068 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er handler WebSocket pour stream server avec gestion des connexions et Ă©vĂ©nements de streaming. ### Fichiers Ă  Modifier - `veza-stream-server/src/routes.rs` - `veza-stream-server/src/main.rs` ### ImplĂ©mentation **Étape 1**: IntĂ©grer WebSocketManager dans routes **Étape 2**: CrĂ©er handler WebSocket avec authentification **Étape 3**: Ajouter gestion des Ă©vĂ©nements de streaming **Étape 4**: Tests handler WebSocket ### Code Snippets **veza-stream-server/src/routes.rs**: ```rust use axum::extract::ws::WebSocketUpgrade; use stream_server::streaming::websocket::websocket_handler; pub fn create_routes() -> Router { Router::new() .route("/ws", get(websocket_handler)) // ... autres routes } ``` ### Definition of Done - [x] Handler WebSocket créé dans routes.rs : Handler WebSocket créé avec route `/ws` dans `routes.rs` - [x] WebSocketManager intĂ©grĂ© dans AppState : `WebSocketManager` intĂ©grĂ© dans `AppState` avec gestion des connexions - [x] Authentification via token JWT : Authentification JWT implĂ©mentĂ©e via query parameters et headers - [x] Gestion des Ă©vĂ©nements de streaming : Gestion complĂšte des Ă©vĂ©nements de streaming (connect, disconnect, play, pause, seek) - [x] Tests handler WebSocket créés : Tests unitaires et d'intĂ©gration pour le handler WebSocket - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **routes.rs** : - Handler WebSocket créé avec route `/ws` - IntĂ©gration avec `WebSocketManager` via `AppState` - Support des query parameters pour authentification - **streaming/websocket.rs** : - Gestion des connexions WebSocket avec `axum::extract::ws` - Authentification via JWT token - Gestion des Ă©vĂ©nements de streaming (play, pause, seek, etc.) - Gestion des erreurs et fermeture propre des connexions - **main.rs** : - Wrapper `websocket_handler_wrapper` pour intĂ©gration avec `AppState` - Configuration CORS pour WebSocket - **Tests** : Tests unitaires et d'intĂ©gration pour vĂ©rifier le fonctionnement du handler --- ## T0074: Add Stream Server Audio Streaming Routes ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-007 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0073 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er routes pour streaming audio avec support range requests et signatures. ### Fichiers Ă  Modifier - `veza-stream-server/src/routes.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er route /stream/:filename **Étape 2**: ImplĂ©menter range requests (HTTP 206) **Étape 3**: Ajouter validation de signatures **Étape 4**: Tests routes streaming ### Code Snippets **veza-stream-server/src/routes.rs**: ```rust async fn stream_audio( Path(filename): Path, headers: HeaderMap, State(state): State, ) -> Result { // ImplĂ©menter range requests et streaming } ``` ### Definition of Done - [x] Route /stream/:filename créée : Route `/stream/:filename` créée dans `routes.rs` avec handler `stream_audio_handler` - [x] Support HTTP Range requests (206) : Support complet des Range requests avec rĂ©ponse HTTP 206 Partial Content - [x] Validation de signatures : Validation des signatures pour sĂ©curiser l'accĂšs aux fichiers audio - [x] Gestion des erreurs : Gestion complĂšte des erreurs (fichier non trouvĂ©, signature invalide, etc.) - [x] Tests routes créés : Tests unitaires et d'intĂ©gration pour les routes de streaming - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **routes.rs** : - Route `/stream/:filename` avec handler `stream_audio_handler` - Route `/stream` avec handler `stream_file_handler` - Route `/metadata` pour obtenir les mĂ©tadonnĂ©es des fichiers - Support des Range requests via fonction `serve_partial_file` - **utils.rs** : - Fonction `serve_partial_file` pour gĂ©rer les Range requests (HTTP 206) - Fonction `validate_filename` pour sĂ©curiser les noms de fichiers - Fonction `build_safe_path` pour construire des chemins sĂ©curisĂ©s - Fonction `validate_signature` pour valider les signatures d'accĂšs - **Gestion des headers** : - Support de `Range` header pour les requĂȘtes partielles - Retour de `Content-Range` et `Accept-Ranges` headers - Gestion de `Content-Type` selon le type de fichier - **Tests** : Tests pour vĂ©rifier le streaming avec Range requests et validation des signatures --- ## T0075: Add Stream Server HLS Playlist Generation ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-008 **Phase**: 1 **Priority**: high **Complexity**: high **Temps EstimĂ©**: 3h **DĂ©pendances**: T0074 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique GĂ©nĂ©rer playlists HLS (.m3u8) pour streaming adaptatif avec diffĂ©rentes qualitĂ©s. ### Fichiers Ă  CrĂ©er - `veza-stream-server/src/streaming/hls.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er HLS playlist generator **Étape 2**: GĂ©nĂ©rer master playlist **Étape 3**: GĂ©nĂ©rer quality playlists **Étape 4**: Tests HLS generation ### Code Snippets **veza-stream-server/src/streaming/hls.rs**: ```rust pub struct HLSGenerator { track_id: String, qualities: Vec, } pub fn generate_master_playlist(&self) -> String { // GĂ©nĂ©rer playlist master.m3u8 } ``` ### Definition of Done - [x] HLSGenerator créé : Structure `HLSGenerator` créée avec support multi-qualitĂ©s - [x] Master playlist generation : GĂ©nĂ©ration de master playlist `.m3u8` avec diffĂ©rentes qualitĂ©s - [x] Quality playlists generation : GĂ©nĂ©ration de playlists spĂ©cifiques par qualitĂ© (low, medium, high) - [x] Support segments .ts : Support pour la gĂ©nĂ©ration et le streaming de segments `.ts` - [x] Tests HLS créés : Tests unitaires pour la gĂ©nĂ©ration de playlists HLS - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **streaming/hls.rs** : - Structure `HLSGenerator` avec support multi-qualitĂ©s - Fonction `generate_master_playlist` pour crĂ©er le master playlist - Fonction `generate_quality_playlist` pour gĂ©nĂ©rer les playlists par qualitĂ© - Support des segments `.ts` avec durĂ©e et sĂ©quence - Gestion des variantes de qualitĂ© (bitrate, resolution) - **streaming/adaptive.rs** : - Handler `hls_master_playlist` pour servir le master playlist - Handler `hls_quality_playlist` pour servir les playlists de qualitĂ© - Validation des signatures pour sĂ©curiser l'accĂšs - Support des paramĂštres de requĂȘte (expires, sig, quality) - **Routes** : - IntĂ©gration des handlers HLS dans le router - Support des headers appropriĂ©s (`Content-Type: application/vnd.apple.mpegurl`) - Gestion du cache avec `Cache-Control: no-cache` - **Tests** : Tests pour vĂ©rifier la gĂ©nĂ©ration correcte des playlists HLS --- ## T0076: Add Stream Server Graceful Shutdown ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-009 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0068 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique ImplĂ©menter graceful shutdown pour fermer les connexions et sauvegarder l'Ă©tat. ### Fichiers Ă  Modifier - `veza-stream-server/src/main.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er signal handler **Étape 2**: Fermer connexions DB **Étape 3**: Fermer connexions WebSocket **Étape 4**: Sauvegarder Ă©tat ### Code Snippets **veza-stream-server/src/main.rs**: ```rust async fn shutdown_signal() { tokio::signal::ctrl_c() .await .expect("Failed to install signal handler"); // Graceful shutdown logic } ``` ### Definition of Done - [x] Signal handler créé : Handler pour SIGINT/SIGTERM avec `tokio::signal::ctrl_c` - [x] Fermeture connexions DB : Fermeture propre des connexions Ă  la base de donnĂ©es - [x] Fermeture connexions WebSocket : Fermeture gracieuse de toutes les connexions WebSocket actives - [x] Sauvegarde Ă©tat : Sauvegarde de l'Ă©tat du serveur avant arrĂȘt - [x] Tests shutdown créés : Tests pour vĂ©rifier le graceful shutdown - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **main.rs** : - Fonction `shutdown_signal()` pour capturer SIGINT/SIGTERM - Utilisation de `axum::serve().with_graceful_shutdown()` pour arrĂȘt gracieux - Fermeture des connexions dans l'ordre appropriĂ© - **Gestion des ressources** : - Fermeture des connexions WebSocket avec notification aux clients - Fermeture des connexions de base de donnĂ©es - ArrĂȘt des tĂąches asynchrones en cours - Sauvegarde de l'Ă©tat du serveur si nĂ©cessaire - **Logging** : - Messages de log pour chaque Ă©tape de l'arrĂȘt - Notification des clients connectĂ©s avant fermeture - **Tests** : Tests pour vĂ©rifier que le graceful shutdown fonctionne correctement --- ## T0077: Add Stream Server Health Check Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-010 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0068 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er endpoint health check avec vĂ©rification DB et services. ### Fichiers Ă  Modifier - `veza-stream-server/src/routes.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er /health endpoint **Étape 2**: VĂ©rifier connexion DB **Étape 3**: VĂ©rifier services **Étape 4**: Retourner statut ### Code Snippets **veza-stream-server/src/routes.rs**: ```rust async fn health_check(State(state): State) -> Json { // VĂ©rifier DB, services, etc. } ``` ### Definition of Done - [x] Endpoint /health créé : Endpoint `/health` créé dans `routes.rs` avec handler `health_check` - [x] VĂ©rification DB : VĂ©rification de la connexion Ă  la base de donnĂ©es avec timeout - [x] VĂ©rification services : VĂ©rification des services critiques (audio directory, WebSocket manager) - [x] Retour statut JSON : Retour d'un JSON avec statut dĂ©taillĂ© de chaque service - [x] Tests health check créés : Tests unitaires et d'intĂ©gration pour le health check - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **routes.rs** : - Handler `health_check` avec vĂ©rifications complĂštes - VĂ©rification de la base de donnĂ©es avec mesure du temps de rĂ©ponse - VĂ©rification du rĂ©pertoire audio - VĂ©rification des services WebSocket - **RĂ©ponse JSON** : - Statut global (`healthy`, `degraded`, `unhealthy`) - DĂ©tails de chaque check avec statut et message - Temps de rĂ©ponse pour chaque service - Informations systĂšme (uptime, version) - **Gestion des erreurs** : - Gestion gracieuse des erreurs de connexion - Timeout pour Ă©viter les blocages - Statut dĂ©gradĂ© si certains services sont indisponibles - **Tests** : Tests pour vĂ©rifier le health check dans diffĂ©rents scĂ©narios --- ## T0078: Add Stream Server Metrics Endpoint ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-011 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0077 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Exposer mĂ©triques Prometheus pour monitoring du stream server. ### Fichiers Ă  CrĂ©er - `veza-stream-server/src/monitoring/metrics.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er mĂ©triques Prometheus **Étape 2**: Exposer endpoint /metrics **Étape 3**: Collecter mĂ©triques streaming **Étape 4**: Tests mĂ©triques ### Code Snippets **veza-stream-server/src/monitoring/metrics.rs**: ```rust use prometheus::{Counter, Histogram, Registry}; pub struct StreamMetrics { pub requests_total: Counter, pub stream_duration: Histogram, } ``` ### Definition of Done - [x] MĂ©triques Prometheus créées : Structure `MetricsManager` avec mĂ©triques Prometheus - [x] Endpoint /metrics exposĂ© : Endpoint `/metrics` créé dans `routes.rs` avec handler `metrics_endpoint` - [x] Collecte mĂ©triques streaming : Collecte de mĂ©triques pour streaming (requĂȘtes, durĂ©e, bande passante) - [x] Tests mĂ©triques créés : Tests pour vĂ©rifier l'exposition des mĂ©triques - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **monitoring/metrics.rs** : - Structure `MetricsManager` avec mĂ©triques Prometheus - Compteurs pour les requĂȘtes totales - Histogrammes pour les durĂ©es de streaming - Gauges pour les connexions actives - **routes.rs** : - Handler `metrics_endpoint` pour exposer les mĂ©triques au format Prometheus - Format texte compatible avec Prometheus - **Collecte de mĂ©triques** : - MĂ©triques de streaming (requĂȘtes, durĂ©e, bande passante) - MĂ©triques de connexions WebSocket - MĂ©triques de performance (latence, erreurs) - **IntĂ©gration** : - IntĂ©gration dans les handlers de streaming - Middleware pour collecter automatiquement les mĂ©triques - **Tests** : Tests pour vĂ©rifier l'exposition correcte des mĂ©triques --- ## T0079: Add Stream Server Error Handling ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-012 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0073 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er gestion d'erreurs centralisĂ©e pour stream server avec types d'erreurs spĂ©cifiques. ### Fichiers Ă  Modifier - `veza-stream-server/src/error.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er types d'erreurs streaming **Étape 2**: ImplĂ©menter conversions **Étape 3**: Ajouter error handlers **Étape 4**: Tests error handling ### Code Snippets **veza-stream-server/src/error.rs**: ```rust pub enum StreamError { FileNotFound, InvalidRange, StreamError(String), } ``` ### Definition of Done - [x] Types d'erreurs créés : Enum `AppError` et `StreamError` créés avec tous les types d'erreurs - [x] Conversions implĂ©mentĂ©es : ImplĂ©mentation de `From` pour conversions entre types d'erreurs - [x] Error handlers ajoutĂ©s : Handlers d'erreurs Axum avec conversion en rĂ©ponses HTTP appropriĂ©es - [x] Tests error handling créés : Tests unitaires pour vĂ©rifier la gestion des erreurs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **error.rs** : - Enum `AppError` avec variants pour tous les types d'erreurs (DB, IO, Validation, etc.) - Enum `StreamError` pour erreurs spĂ©cifiques au streaming - ImplĂ©mentation de `std::error::Error` pour compatibilitĂ© - ImplĂ©mentation de `IntoResponse` pour conversion en rĂ©ponses Axum - **Conversions** : - Conversion depuis `sqlx::Error` vers `AppError` - Conversion depuis `std::io::Error` vers `AppError` - Conversion depuis `serde_json::Error` vers `AppError` - **Handlers** : - Middleware pour capturer et formater les erreurs - RĂ©ponses HTTP appropriĂ©es selon le type d'erreur (400, 404, 500, etc.) - Messages d'erreur clairs pour le client - **Tests** : Tests pour vĂ©rifier que les erreurs sont correctement gĂ©rĂ©es et converties --- ## T0080: Add Stream Server Integration Tests ✅ COMPLÉTÉE **Feature Parente**: FEAT-STREAM-013 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0079 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er tests d'intĂ©gration pour stream server (routes, WebSocket, streaming). ### Fichiers Ă  CrĂ©er - `veza-stream-server/tests/integration_test.rs` ### ImplĂ©mentation **Étape 1**: Setup test server **Étape 2**: Tests routes streaming **Étape 3**: Tests WebSocket **Étape 4**: Tests HLS generation ### Code Snippets **veza-stream-server/tests/integration_test.rs**: ```rust #[tokio::test] async fn test_stream_endpoint() { // Test streaming endpoint } ``` ### Definition of Done - [x] Tests d'intĂ©gration créés : Suite de tests d'intĂ©gration dans `tests/integration_test.rs` - [x] Tests routes streaming : Tests pour les routes de streaming avec Range requests - [x] Tests WebSocket : Tests pour les connexions WebSocket et Ă©vĂ©nements - [x] Tests HLS : Tests pour la gĂ©nĂ©ration de playlists HLS - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **tests/integration_test.rs** : - Setup de serveur de test avec Ă©tat isolĂ© - Tests pour les routes de streaming - Tests pour les connexions WebSocket - Tests pour la gĂ©nĂ©ration HLS - **Tests streaming** : - Tests avec Range requests (HTTP 206) - Tests de validation de signatures - Tests de gestion des erreurs (fichier non trouvĂ©, etc.) - **Tests WebSocket** : - Tests de connexion et authentification - Tests d'envoi/rĂ©ception de messages - Tests de gestion des dĂ©connexions - **Tests HLS** : - Tests de gĂ©nĂ©ration de master playlist - Tests de gĂ©nĂ©ration de quality playlists - Tests de validation des playlists gĂ©nĂ©rĂ©es - **Infrastructure de test** : - Fixtures pour les fichiers audio de test - Helpers pour crĂ©er des requĂȘtes de test - Isolation des tests avec Ă©tat propre --- ## T0081: Create Common Library Structure ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-001 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er structure de base pour la bibliothĂšque commune (types partagĂ©s, utilities). ### Fichiers Ă  CrĂ©er - `veza-common/src/lib.rs` - `veza-common/src/types.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er workspace veza-common **Étape 2**: DĂ©finir types partagĂ©s **Étape 3**: CrĂ©er utilities communes **Étape 4**: Configurer Cargo.toml ### Code Snippets **veza-common/src/lib.rs**: ```rust pub mod types; pub mod utils; pub use types::*; ``` ### Definition of Done - [x] Structure veza-common créée : Structure de base créée avec `src/lib.rs` et modules organisĂ©s - [x] Types partagĂ©s dĂ©finis : Types de base dĂ©finis dans `src/types/` pour User, Track, Playlist - [x] Utilities communes créées : Modules utilitaires créés (validation, serialization, date, logging) - [x] Cargo.toml configurĂ© : Configuration Cargo avec dĂ©pendances nĂ©cessaires (serde, uuid, etc.) - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/lib.rs** : - Modules publics organisĂ©s (types, error, utils, config) - Re-exports pour faciliter l'utilisation - Structure modulaire claire - **Cargo.toml** : - DĂ©pendances de base (serde, uuid, chrono, etc.) - Configuration pour ĂȘtre utilisĂ©e comme bibliothĂšque - Workspace configuration si nĂ©cessaire - **Structure des modules** : - `src/types/` : Types partagĂ©s (User, Track, Playlist) - `src/error.rs` : Types d'erreurs communs - `src/utils/` : Utilitaires (validation, serialization, date, logging) - `src/config/` : Types de configuration partagĂ©s - **Documentation** : Documentation de base avec doc comments --- ## T0082: Add Common Library Shared Types ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0081 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er types partagĂ©s (User, Track, Playlist) utilisables par tous les services. ### Fichiers Ă  CrĂ©er - `veza-common/src/types/user.rs` - `veza-common/src/types/track.rs` - `veza-common/src/types/playlist.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er User type **Étape 2**: CrĂ©er Track type **Étape 3**: CrĂ©er Playlist type **Étape 4**: Ajouter Serialize/Deserialize ### Code Snippets **veza-common/src/types/user.rs**: ```rust #[derive(Debug, Clone, Serialize, Deserialize)] pub struct User { pub id: i64, pub username: String, pub email: String, } ``` ### Definition of Done - [x] Types User, Track, Playlist créés : Types complets créés dans `src/types/` avec tous les champs nĂ©cessaires - [x] Serialize/Deserialize implĂ©mentĂ© : DĂ©rive `Serialize` et `Deserialize` pour tous les types - [x] Validation avec Zod/serde : Validation des types avec serde (pas de Zod cĂŽtĂ© Rust, mais validation des champs) - [x] Tests types créés : Tests unitaires pour vĂ©rifier la sĂ©rialisation/dĂ©sĂ©rialisation - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/types/user.rs** : - Structure `User` avec champs (id, username, email, avatar_url, etc.) - DĂ©rive `Serialize`, `Deserialize`, `Clone`, `Debug` - Validation des champs (email format, username length) - **src/types/track.rs** : - Structure `Track` avec mĂ©tadonnĂ©es complĂštes - Champs (id, title, artist, duration, file_path, format, etc.) - Relations avec User (owner_id) - **src/types/playlist.rs** : - Structure `Playlist` avec champs (id, name, description, tracks, owner_id, etc.) - Support pour playlists publiques/privĂ©es - **SĂ©rialisation** : - Configuration serde pour JSON (snake_case, etc.) - Support des options (skip_serializing_if, etc.) - **Tests** : Tests pour vĂ©rifier la sĂ©rialisation/dĂ©sĂ©rialisation correcte --- ## T0083: Add Common Library Error Types ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-003 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h **DĂ©pendances**: T0081 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er types d'erreurs partagĂ©s pour tous les services. ### Fichiers Ă  CrĂ©er - `veza-common/src/error.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er Error enum **Étape 2**: ImplĂ©menter conversions **Étape 3**: Ajouter error codes **Étape 4**: Tests error types ### Code Snippets **veza-common/src/error.rs**: ```rust #[derive(Debug, Error)] pub enum CommonError { NotFound, ValidationError(String), InternalError(String), } ``` ### Definition of Done - [x] Error enum créé : Enum `CommonError` créé avec tous les types d'erreurs communs - [x] Conversions implĂ©mentĂ©es : ImplĂ©mentation de `From` pour conversions depuis erreurs standards - [x] Error codes dĂ©finis : Codes d'erreur dĂ©finis pour chaque type d'erreur - [x] Tests error types créés : Tests unitaires pour vĂ©rifier les conversions d'erreurs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/error.rs** : - Enum `CommonError` avec variants (NotFound, ValidationError, InternalError, etc.) - ImplĂ©mentation de `std::error::Error` - ImplĂ©mentation de `Display` pour messages d'erreur - Codes d'erreur HTTP associĂ©s - **Conversions** : - Conversion depuis `serde_json::Error` - Conversion depuis `std::io::Error` - Conversion depuis autres types d'erreurs standards - **Format d'erreur** : - Format JSON standardisĂ© pour les erreurs - Messages d'erreur clairs et informatifs - Support des erreurs contextuelles - **Tests** : Tests pour vĂ©rifier les conversions et le formatage des erreurs --- ## T0084: Add Common Library Validation Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-004 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0082 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er utilities de validation partagĂ©es (email, username, etc.). ### Fichiers Ă  CrĂ©er - `veza-common/src/utils/validation.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er validators **Étape 2**: Validation email **Étape 3**: Validation username **Étape 4**: Tests validation ### Code Snippets **veza-common/src/utils/validation.rs**: ```rust pub fn validate_email(email: &str) -> bool { // Validation email } pub fn validate_username(username: &str) -> bool { // Validation username } ``` ### Definition of Done - [x] Validators créés : Fonctions de validation créées dans `src/utils/validation.rs` - [x] Validation email : Fonction `validate_email` avec regex pour validation d'email - [x] Validation username : Fonction `validate_username` avec rĂšgles (longueur, caractĂšres autorisĂ©s) - [x] Tests validation créés : Tests unitaires pour toutes les fonctions de validation - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/utils/validation.rs** : - Fonction `validate_email` avec regex RFC 5322 - Fonction `validate_username` avec rĂšgles (3-30 caractĂšres, alphanumĂ©rique + underscore) - Fonction `validate_password` avec rĂšgles de sĂ©curitĂ© - Fonction `validate_url` pour validation d'URLs - **Validation avancĂ©e** : - Validation de format de fichiers - Validation de nombres (port, ID, etc.) - Validation de dates et timestamps - **Messages d'erreur** : - Messages d'erreur clairs pour chaque type de validation - Support de la localisation si nĂ©cessaire - **Tests** : Tests complets couvrant tous les cas (valides, invalides, edge cases) --- ## T0085: Add Common Library Serialization Helpers ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-005 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0082 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er helpers pour sĂ©rialisation/dĂ©sĂ©rialisation avec serde. ### Fichiers Ă  CrĂ©er - `veza-common/src/utils/serialization.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er serialization helpers **Étape 2**: JSON serialization **Étape 3**: Error handling **Étape 4**: Tests serialization ### Code Snippets **veza-common/src/utils/serialization.rs**: ```rust pub fn to_json(value: &T) -> Result { serde_json::to_string(value) } ``` ### Definition of Done - [x] Serialization helpers créés : Helpers créés dans `src/utils/serialization.rs` pour faciliter la sĂ©rialisation - [x] JSON serialization : Fonctions `to_json` et `from_json` pour sĂ©rialisation JSON - [x] Error handling : Gestion d'erreurs avec `Result` pour toutes les opĂ©rations de sĂ©rialisation - [x] Tests serialization créés : Tests unitaires pour vĂ©rifier la sĂ©rialisation/dĂ©sĂ©rialisation - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/utils/serialization.rs** : - Fonction `to_json` pour sĂ©rialiser en JSON - Fonction `from_json` pour dĂ©sĂ©rialiser depuis JSON - Fonction `to_json_pretty` pour JSON formatĂ© (debug) - Helpers pour sĂ©rialisation de types spĂ©cifiques - **Error handling** : - Utilisation de `Result` pour gestion d'erreurs - Conversion vers `CommonError` pour cohĂ©rence - **Configuration** : - Support des options de sĂ©rialisation (skip_none, etc.) - Support de diffĂ©rents formats si nĂ©cessaire - **Tests** : Tests pour vĂ©rifier la sĂ©rialisation/dĂ©sĂ©rialisation correcte de diffĂ©rents types --- ## T0086: Add Common Library Date Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-006 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0081 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er utilities pour manipulation de dates (format, parsing, timezone). ### Fichiers Ă  CrĂ©er - `veza-common/src/utils/date.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er date utilities **Étape 2**: Format dates **Étape 3**: Parse dates **Étape 4**: Tests date utilities ### Code Snippets **veza-common/src/utils/date.rs**: ```rust pub fn format_timestamp(ts: i64) -> String { // Format timestamp } ``` ### Definition of Done - [x] Date utilities créées : Utilities créées dans `src/utils/date.rs` pour manipulation de dates - [x] Format dates : Fonctions pour formater les dates dans diffĂ©rents formats (ISO 8601, RFC 3339, etc.) - [x] Parse dates : Fonctions pour parser les dates depuis diffĂ©rents formats - [x] Tests date utilities créés : Tests unitaires pour toutes les fonctions de manipulation de dates - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/utils/date.rs** : - Fonction `format_timestamp` pour formater un timestamp en string - Fonction `parse_timestamp` pour parser une string en timestamp - Fonction `format_datetime` pour formater DateTime avec timezone - Fonction `parse_datetime` pour parser DateTime avec timezone - Fonctions utilitaires (now, add_duration, etc.) - **Support timezone** : - Utilisation de `chrono` pour gestion des timezones - Conversion entre timezones - Format UTC par dĂ©faut - **Formats supportĂ©s** : - ISO 8601 - RFC 3339 - Formats personnalisĂ©s - **Tests** : Tests pour vĂ©rifier le formatage et le parsing corrects --- ## T0087: Add Common Library Logging Utilities ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-007 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0081 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er utilities de logging partagĂ©es pour tous les services Rust. ### Fichiers Ă  CrĂ©er - `veza-common/src/utils/logging.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er logging utilities **Étape 2**: Format logs **Étape 3**: Context logging **Étape 4**: Tests logging ### Code Snippets **veza-common/src/utils/logging.rs**: ```rust pub fn log_request(service: &str, method: &str, path: &str) { // Log request } ``` ### Definition of Done - [x] Logging utilities créées : Utilities créées dans `src/utils/logging.rs` pour logging structurĂ© - [x] Format logs : Formatage des logs avec contexte structurĂ© (service, method, path, etc.) - [x] Context logging : Support du logging contextuel avec champs additionnels - [x] Tests logging créés : Tests unitaires pour vĂ©rifier le formatage des logs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/utils/logging.rs** : - Fonction `log_request` pour logger les requĂȘtes HTTP - Fonction `log_error` pour logger les erreurs avec contexte - Fonction `log_info` pour logger des informations avec contexte - Support du logging structurĂ© avec champs additionnels - **Integration** : - Integration avec `tracing` pour logging structurĂ© - Support des spans pour traçage - Format JSON pour logs structurĂ©s - **Context** : - Support des champs de contexte (request_id, user_id, etc.) - Propagation du contexte entre appels - **Tests** : Tests pour vĂ©rifier le formatage correct des logs --- ## T0088: Add Common Library Config Types ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-008 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0081 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er types de configuration partagĂ©s (Database, Redis, etc.). ### Fichiers Ă  CrĂ©er - `veza-common/src/config/database.rs` - `veza-common/src/config/redis.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er DatabaseConfig **Étape 2**: CrĂ©er RedisConfig **Étape 3**: Ajouter validation **Étape 4**: Tests config ### Code Snippets **veza-common/src/config/database.rs**: ```rust #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DatabaseConfig { pub url: String, pub max_connections: u32, } ``` ### Definition of Done - [x] Config types créés : Types de configuration créés dans `src/config/` pour tous les services - [x] DatabaseConfig : Structure `DatabaseConfig` avec champs (url, max_connections, pool_size, etc.) - [x] RedisConfig : Structure `RedisConfig` avec champs (url, password, db, etc.) - [x] Tests config créés : Tests unitaires pour vĂ©rifier la validation et le parsing de la configuration - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **src/config/database.rs** : - Structure `DatabaseConfig` avec tous les champs nĂ©cessaires - Validation des champs (URL format, pool size limits, etc.) - Support de la dĂ©sĂ©rialisation depuis variables d'environnement - **src/config/redis.rs** : - Structure `RedisConfig` avec configuration Redis complĂšte - Support de la connexion avec/sans authentification - Configuration du pool de connexions - **Validation** : - Validation des URLs et formats - Validation des valeurs numĂ©riques (ports, timeouts, etc.) - Messages d'erreur clairs pour configuration invalide - **Tests** : Tests pour vĂ©rifier la validation et le parsing corrects --- ## T0089: Add Common Library Tests Setup ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-009 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0088 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer infrastructure de tests pour la bibliothĂšque commune. ### Fichiers Ă  CrĂ©er - `veza-common/tests/common_tests.rs` ### ImplĂ©mentation **Étape 1**: CrĂ©er test setup **Étape 2**: Test fixtures **Étape 3**: Test helpers **Étape 4**: Tests examples ### Code Snippets **veza-common/tests/common_tests.rs**: ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_common_utilities() { // Tests } } ``` ### Definition of Done - [x] Test setup créé : Infrastructure de tests créée dans `tests/` avec setup et helpers - [x] Test fixtures : Fixtures créées pour les types communs (User, Track, Playlist) - [x] Test helpers : Helpers créés pour faciliter l'Ă©criture de tests (create_test_user, etc.) - [x] Tests examples créés : Exemples de tests créés pour dĂ©montrer l'utilisation - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **tests/common_tests.rs** : - Setup de tests avec fixtures communes - Helpers pour crĂ©er des instances de test - Helpers pour assertions communes - **Test fixtures** : - Fonctions pour crĂ©er des instances de test (User, Track, Playlist) - DonnĂ©es de test rĂ©alistes et variĂ©es - Support des scĂ©narios de test communs - **Test helpers** : - Fonctions utilitaires pour les tests (assertions, validations, etc.) - Helpers pour sĂ©rialisation/dĂ©sĂ©rialisation dans tests - Helpers pour validation dans tests - **Exemples** : Tests d'exemple pour montrer l'utilisation de la bibliothĂšque --- ## T0090: Add Common Library Documentation ✅ COMPLÉTÉE **Feature Parente**: FEAT-COMMON-010 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0089 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Ajouter documentation complĂšte pour la bibliothĂšque commune (README, doc comments). ### Fichiers Ă  CrĂ©er - `veza-common/README.md` ### ImplĂ©mentation **Étape 1**: CrĂ©er README.md **Étape 2**: Ajouter doc comments **Étape 3**: Exemples d'usage **Étape 4**: Documentation API ### Code Snippets **veza-common/README.md**: ```markdown # Veza Common Library BibliothĂšque commune pour tous les services Veza. ``` ### Definition of Done - [x] README.md créé : README.md créé avec description complĂšte de la bibliothĂšque - [x] Doc comments ajoutĂ©s : Doc comments Rust ajoutĂ©s pour toutes les fonctions publiques - [x] Exemples d'usage : Exemples d'utilisation créés dans la documentation - [x] Documentation API : Documentation API complĂšte gĂ©nĂ©rable avec `cargo doc` - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **README.md** : - Description de la bibliothĂšque commune et de son objectif - Instructions d'installation et d'utilisation - Exemples de code pour chaque module - Documentation des modules principaux (types, error, utils, config) - **Doc comments** : - Doc comments Rust pour toutes les fonctions publiques - Exemples de code dans les doc comments - Documentation des types et structures publiques - **Exemples** : - Exemples d'utilisation dans `examples/` si nĂ©cessaire - Exemples dans la documentation README - **Documentation gĂ©nĂ©rĂ©e** : - Documentation gĂ©nĂ©rable avec `cargo doc --open` - Documentation dĂ©ployable si nĂ©cessaire --- ## T0091: Add Frontend TypeScript Strict Mode ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-004 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0072 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Activer TypeScript strict mode dans tsconfig pour meilleure sĂ©curitĂ© de types. ### Fichiers Ă  Modifier - `apps/web/tsconfig.app.json` ### ImplĂ©mentation **Étape 1**: Activer strict mode **Étape 2**: Configurer strict flags **Étape 3**: Corriger erreurs TypeScript **Étape 4**: VĂ©rifier compilation ### Code Snippets **apps/web/tsconfig.app.json**: ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true } } ``` ### Definition of Done - [x] Strict mode activĂ© : `strict: true` activĂ© dans `tsconfig.app.json` - [x] Strict flags configurĂ©s : Tous les flags strict configurĂ©s (noImplicitAny, strictNullChecks, etc.) - [x] Erreurs TypeScript corrigĂ©es : Toutes les erreurs TypeScript corrigĂ©es dans le codebase - [x] Compilation rĂ©ussie : Compilation TypeScript rĂ©ussie sans erreurs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **tsconfig.app.json** : - `strict: true` activĂ© pour activer tous les checks stricts - `noImplicitAny: true` pour interdire les types `any` implicites - `strictNullChecks: true` pour vĂ©rifier les null/undefined - `strictFunctionTypes: true` pour vĂ©rifier les types de fonctions - `strictPropertyInitialization: true` pour vĂ©rifier l'initialisation des propriĂ©tĂ©s - **Corrections** : - Correction des types implicites `any` - Ajout de vĂ©rifications null/undefined - Correction des initialisations de propriĂ©tĂ©s - **Validation** : - Compilation rĂ©ussie avec `tsc --noEmit` - VĂ©rification dans le build process --- ## T0092: Add Frontend ESLint Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-005 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0072 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer ESLint avec rĂšgles React/TypeScript pour maintenir la qualitĂ© du code. ### Fichiers Ă  Modifier - `apps/web/eslint.config.js` ### ImplĂ©mentation **Étape 1**: Configurer ESLint **Étape 2**: Ajouter rĂšgles React **Étape 3**: Ajouter rĂšgles TypeScript **Étape 4**: Tests linting ### Code Snippets **apps/web/eslint.config.js**: ```javascript export default { rules: { 'react-hooks/rules-of-hooks': 'error', '@typescript-eslint/no-unused-vars': 'error', }, }; ``` ### Definition of Done - [x] ESLint configurĂ© : Configuration ESLint complĂšte dans `eslint.config.js` (ou `.eslintrc`) - [x] RĂšgles React ajoutĂ©es : RĂšgles React et React Hooks configurĂ©es - [x] RĂšgles TypeScript ajoutĂ©es : RĂšgles TypeScript configurĂ©es avec `@typescript-eslint` - [x] Tests linting passent : Linting passe sans erreurs sur tout le codebase - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **eslint.config.js** : - Configuration ESLint avec plugins React et TypeScript - RĂšgles React Hooks (rules-of-hooks, exhaustive-deps) - RĂšgles TypeScript (no-unused-vars, no-explicit-any, etc.) - RĂšgles d'accessibilitĂ© (jsx-a11y) - Configuration des parsers et extensions - **IntĂ©gration** : - IntĂ©gration avec Vite/IDE pour feedback en temps rĂ©el - Script npm pour linting (`npm run lint`) - PrĂ©-commit hooks si nĂ©cessaire - **Corrections** : - Correction des erreurs de linting dans le codebase - Configuration des rĂšgles selon les standards du projet --- ## T0093: Add Frontend Prettier Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-006 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0092 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer Prettier pour formatage automatique du code. ### Fichiers Ă  CrĂ©er - `apps/web/.prettierrc.json` ### ImplĂ©mentation **Étape 1**: CrĂ©er .prettierrc.json **Étape 2**: Configurer rĂšgles formatage **Étape 3**: Ajouter .prettierignore **Étape 4**: Tests formatage ### Code Snippets **apps/web/.prettierrc.json**: ```json { "semi": true, "singleQuote": true, "tabWidth": 2 } ``` ### Definition of Done - [x] Prettier configurĂ© : Configuration Prettier créée dans `.prettierrc.json` - [x] RĂšgles formatage dĂ©finies : RĂšgles de formatage dĂ©finies (semi, singleQuote, tabWidth, etc.) - [x] .prettierignore créé : Fichier `.prettierignore` créé pour exclure certains fichiers - [x] Tests formatage passent : Formatage automatique fonctionne correctement - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **.prettierrc.json** : - Configuration Prettier avec rĂšgles (semi, singleQuote, tabWidth, trailingComma, etc.) - Configuration pour TypeScript, JSON, Markdown - **.prettierignore** : - Exclusion de `node_modules`, `dist`, `build`, etc. - Exclusion des fichiers gĂ©nĂ©rĂ©s - **IntĂ©gration** : - IntĂ©gration avec ESLint (eslint-config-prettier) - Script npm pour formatage (`npm run format`) - Formatage automatique dans l'IDE - PrĂ©-commit hooks si nĂ©cessaire --- ## T0094: Add Frontend Component Structure ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-007 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0071 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er structure de base pour les composants React (layout, UI, features). ### Fichiers Ă  CrĂ©er - `apps/web/src/components/base/Button.tsx` - `apps/web/src/components/base/Input.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er structure dossiers **Étape 2**: CrĂ©er composants de base **Étape 3**: Ajouter exports **Étape 4**: Tests structure ### Code Snippets **apps/web/src/components/base/Button.tsx**: ```typescript export const Button = ({ children, onClick }: ButtonProps) => { return ; }; ``` ### Definition of Done - [x] Structure dossiers créée : Structure organisĂ©e créée (`components/ui/`, `components/layout/`, `features/`) - [x] Composants de base créés : Composants UI de base créés (Button, Input, Card, etc.) - [x] Exports configurĂ©s : Exports organisĂ©s avec index.ts pour faciliter les imports - [x] Tests structure passent : Tests pour vĂ©rifier la structure et les composants - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **Structure** : - `src/components/ui/` : Composants UI rĂ©utilisables (Button, Input, Card, etc.) - `src/components/layout/` : Composants de layout (Header, Sidebar, Footer, etc.) - `src/features/` : Features organisĂ©es par domaine (auth, player, library, etc.) - `src/pages/` : Pages de l'application - **Composants UI** : - Button avec variants (primary, secondary, destructive, etc.) - Input avec validation et Ă©tats - Card pour affichage de contenu - Autres composants UI selon besoins - **Exports** : - Index.ts pour faciliter les imports - Organisation cohĂ©rente des exports --- ## T0095: Add Frontend State Management Setup ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-008 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0072 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer Zustand pour state management avec stores de base. ### Fichiers Ă  CrĂ©er - `apps/web/src/stores/auth.ts` - `apps/web/src/stores/player.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er auth store **Étape 2**: CrĂ©er player store **Étape 3**: Configurer persistence **Étape 4**: Tests stores ### Code Snippets **apps/web/src/stores/auth.ts**: ```typescript export const useAuthStore = create((set) => ({ user: null, login: (user) => set({ user }), })); ``` ### Definition of Done - [x] Auth store créé : Store Zustand `auth.ts` créé avec gestion de l'authentification - [x] Player store créé : Store Zustand `player.ts` créé pour gestion du lecteur audio - [x] Persistence configurĂ©e : Persistence avec localStorage pour auth et player state - [x] Tests stores créés : Tests unitaires pour les stores Zustand - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **stores/auth.ts** : - État d'authentification (user, isAuthenticated, token) - Actions (login, logout, register, checkAuthStatus) - Gestion du token JWT dans localStorage - Persistence de l'Ă©tat d'authentification - **stores/player.ts** : - État du lecteur (currentTrack, isPlaying, volume, position, queue) - Actions (play, pause, next, previous, setVolume, seek) - Gestion de la queue de lecture - **Autres stores** : - `ui.ts` : État UI (theme, sidebar, modals) - `chat.ts` : État du chat (messages, rooms, activeRoom) - `library.ts` : État de la bibliothĂšque (tracks, playlists) - **Tests** : Tests unitaires pour chaque store avec Vitest --- ## T0096: Add Frontend Router Configuration ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-009 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h **DĂ©pendances**: T0095 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer React Router avec routes de base et protection d'authentification. ### Fichiers Ă  CrĂ©er - `apps/web/src/router/index.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er router **Étape 2**: DĂ©finir routes **Étape 3**: Ajouter protection auth **Étape 4**: Tests router ### Code Snippets **apps/web/src/router/index.tsx**: ```typescript export const AppRouter = () => ( } /> } /> ); ``` ### Definition of Done - [x] Router créé : Router React Router créé dans `src/router/index.tsx` - [x] Routes dĂ©finies : Routes de base dĂ©finies (home, login, register, dashboard, etc.) - [x] Protection auth ajoutĂ©e : Protection d'authentification avec `ProtectedRoute` component - [x] Tests router créés : Tests pour vĂ©rifier le routing et la protection - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **router/index.tsx** : - Configuration React Router avec `BrowserRouter` et `Routes` - Routes publiques (login, register, home) - Routes protĂ©gĂ©es (dashboard, profile, settings) - Route 404 pour pages non trouvĂ©es - **Protection** : - Composant `ProtectedRoute` pour protĂ©ger les routes authentifiĂ©es - Redirection vers `/login` si non authentifiĂ© - VĂ©rification de l'Ă©tat d'authentification via auth store - **Navigation** : - Composants de navigation (Link, NavLink) - Navigation programmatique avec `useNavigate` - **Tests** : Tests pour vĂ©rifier le routing et la protection d'authentification --- ## T0097: Add Frontend Environment Variables Setup ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-010 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0072 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer variables d'environnement pour frontend avec validation et types. ### Fichiers Ă  CrĂ©er - `apps/web/.env.example` - `apps/web/src/config/env.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er .env.example **Étape 2**: CrĂ©er env.ts avec validation **Étape 3**: Ajouter types TypeScript **Étape 4**: Tests env ### Code Snippets **apps/web/src/config/env.ts**: ```typescript export const env = { API_URL: import.meta.env.VITE_API_URL, WS_URL: import.meta.env.VITE_WS_URL, }; ``` ### Definition of Done - [x] .env.example créé : Fichier `.env.example` créé avec toutes les variables nĂ©cessaires - [x] env.ts avec validation : Module `env.ts` créé avec validation Zod des variables - [x] Types TypeScript : Types TypeScript pour les variables d'environnement - [x] Tests env créés : Tests pour vĂ©rifier la validation des variables - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **.env.example** : - Variables d'environnement documentĂ©es (VITE_API_URL, VITE_WS_URL, etc.) - Valeurs d'exemple pour chaque variable - Documentation des variables nĂ©cessaires - **config/env.ts** : - Validation avec Zod pour toutes les variables - Types TypeScript infĂ©rĂ©s depuis le schema Zod - Messages d'erreur clairs si variables manquantes - Variables avec valeurs par dĂ©faut si appropriĂ© - **Types** : - Types TypeScript pour `import.meta.env` - AutocomplĂ©tion pour les variables d'environnement - **Tests** : Tests pour vĂ©rifier la validation et les valeurs par dĂ©faut --- ## T0098: Add Frontend Error Boundary ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-011 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h **DĂ©pendances**: T0094 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er Error Boundary React pour capturer et afficher les erreurs de maniĂšre gracieuse. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ErrorBoundary.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er ErrorBoundary **Étape 2**: GĂ©rer erreurs **Étape 3**: Afficher UI erreur **Étape 4**: Tests ErrorBoundary ### Code Snippets **apps/web/src/components/ErrorBoundary.tsx**: ```typescript export class ErrorBoundary extends Component { state = { hasError: false }; static getDerivedStateFromError() { return { hasError: true }; } } ``` ### Definition of Done - [x] ErrorBoundary créé : Composant `ErrorBoundary` créé avec gestion d'erreurs React - [x] Gestion erreurs : Gestion des erreurs avec `componentDidCatch` et `getDerivedStateFromError` - [x] UI erreur affichĂ©e : UI d'erreur affichĂ©e avec message et option de rĂ©essayer - [x] Tests ErrorBoundary créés : Tests pour vĂ©rifier la capture et l'affichage des erreurs - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **components/ErrorBoundary.tsx** : - Composant class avec `componentDidCatch` pour capturer les erreurs - `getDerivedStateFromError` pour mettre Ă  jour l'Ă©tat - UI d'erreur avec message et bouton de rĂ©essai - Logging des erreurs pour debugging - **IntĂ©gration** : - ErrorBoundary intĂ©grĂ© dans l'App principal - ErrorBoundary pour les routes spĂ©cifiques si nĂ©cessaire - **UI** : - Message d'erreur clair et informatif - Bouton pour rĂ©essayer ou retourner Ă  l'accueil - Design cohĂ©rent avec le reste de l'application - **Tests** : Tests pour vĂ©rifier la capture et l'affichage des erreurs --- ## T0099: Add Frontend Loading States ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-012 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0094 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique CrĂ©er composants de loading states (spinner, skeleton) pour meilleure UX. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/LoadingSpinner.tsx` - `apps/web/src/components/ui/Skeleton.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er LoadingSpinner **Étape 2**: CrĂ©er Skeleton **Étape 3**: Ajouter animations **Étape 4**: Tests loading states ### Code Snippets **apps/web/src/components/ui/LoadingSpinner.tsx**: ```typescript export const LoadingSpinner = () => (
Loading...
); ``` ### Definition of Done - [x] LoadingSpinner créé : Composant `LoadingSpinner` créé avec tailles personnalisables (sm, md, lg) - [x] Skeleton créé : Composant `Skeleton` créé avec variants (text, circular, rectangular) - [x] Animations ajoutĂ©es : Animations CSS (spin, pulse, shimmer) pour les Ă©tats de chargement - [x] Tests loading states créés : Tests unitaires pour LoadingSpinner et Skeleton - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **components/ui/LoadingSpinner.tsx** : - Composant avec props pour taille (sm, md, lg) et texte optionnel - Animation `animate-spin` avec Tailwind CSS - Support dark mode - AccessibilitĂ© avec `role="status"` et `aria-label` - **components/ui/Skeleton.tsx** : - Composant avec variants (text, circular, rectangular) - Animations (pulse, wave, none) - Support de dimensions personnalisables (width, height) - AccessibilitĂ© avec `aria-hidden="true"` - **Animations CSS** : - Animation `shimmer` dans `index.css` pour effet de vague - Animation `pulse` de Tailwind pour effet de pulsation - **Tests** : Tests unitaires pour vĂ©rifier le rendu et les props des composants --- ## T0100: Add Frontend Test Setup ✅ COMPLÉTÉE **Feature Parente**: FEAT-FRONT-013 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0099 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-11-03 ### Description Technique Configurer infrastructure de tests (Vitest, Testing Library) avec setup et helpers. ### Fichiers Ă  CrĂ©er - `apps/web/src/test/setup.ts` - `apps/web/src/test/helpers.tsx` ### ImplĂ©mentation **Étape 1**: Configurer Vitest **Étape 2**: Configurer Testing Library **Étape 3**: CrĂ©er test helpers **Étape 4**: Tests setup ### Code Snippets **apps/web/src/test/setup.ts**: ```typescript import { afterEach } from 'vitest'; import { cleanup } from '@testing-library/react'; afterEach(() => { cleanup(); }); ``` ### Definition of Done - [x] Vitest configurĂ© : Configuration Vitest complĂšte dans `vitest.config.ts` avec globals et jsdom - [x] Testing Library configurĂ© : Testing Library configurĂ© avec setup dans `src/test/setup.ts` - [x] Test helpers créés : Helpers créés dans `src/test/helpers.tsx` avec providers (Router, QueryClient) - [x] Tests setup passent : Tests de setup passent pour vĂ©rifier la configuration - [x] Code review approuvĂ© **DĂ©tails de l'implĂ©mentation**: - **vitest.config.ts** : - Configuration Vitest avec `globals: true` et `environment: 'jsdom'` - Setup files configurĂ©s (`src/test/setup.ts`) - Path aliases configurĂ©s pour correspondre Ă  Vite - Configuration de coverage avec seuils Ă  80% - **test/setup.ts** : - Import de `@testing-library/jest-dom` pour matchers - Cleanup aprĂšs chaque test avec `afterEach(cleanup)` - Mocks pour APIs du navigateur (matchMedia, localStorage, WebSocket) - Mocks pour variables d'environnement - **test/helpers.tsx** : - Fonction `customRender` avec providers (BrowserRouter, QueryClientProvider) - Re-export de toutes les fonctions de Testing Library - QueryClient configurĂ© pour tests (retry: false, refetchOnWindowFocus: false) - **Tests** : Tests pour vĂ©rifier que le setup fonctionne correctement --- ## T0101: Add Frontend Authentication Pages ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-014 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0100 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er pages d'authentification (Login, Register) avec formulaires et validation. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/pages/LoginPage.tsx` - `apps/web/src/features/auth/pages/RegisterPage.tsx` - `apps/web/src/features/auth/components/LoginForm.tsx` - `apps/web/src/features/auth/components/RegisterForm.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er LoginPage avec LoginForm **Étape 2**: CrĂ©er RegisterPage avec RegisterForm **Étape 3**: Ajouter validation avec react-hook-form + zod **Étape 4**: IntĂ©grer avec auth store **Étape 5**: Tests pages auth ### Code Snippets **apps/web/src/features/auth/pages/LoginPage.tsx**: ```typescript import { LoginForm } from '../components/LoginForm'; export function LoginPage() { return (
); } ``` ### Definition of Done - [x] LoginPage créée - [x] RegisterPage créée - [x] LoginForm avec validation - [x] RegisterForm avec validation - [x] IntĂ©gration auth store - [x] Tests pages auth créés - [x] Code review approuvĂ© --- ## T0102: Add Frontend Protected Route Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-015 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0101 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant ProtectedRoute pour protĂ©ger les routes authentifiĂ©es. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/auth/ProtectedRoute.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er ProtectedRoute component **Étape 2**: VĂ©rifier authentification **Étape 3**: Redirection si non authentifiĂ© **Étape 4**: Tests ProtectedRoute ### Code Snippets **apps/web/src/components/auth/ProtectedRoute.tsx**: ```typescript export function ProtectedRoute({ children }: { children: React.ReactNode }) { const { isAuthenticated } = useAuthStore(); if (!isAuthenticated) { return ; } return <>{children}; } ``` ### Definition of Done - [x] ProtectedRoute créé - [x] VĂ©rification authentification - [x] Redirection login - [x] Tests ProtectedRoute créés - [x] Code review approuvĂ© --- ## T0103: Add Frontend Dashboard Layout ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-016 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0102 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er layout principal du dashboard avec sidebar, header, navigation. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/layout/DashboardLayout.tsx` - `apps/web/src/components/layout/Sidebar.tsx` - `apps/web/src/components/layout/Header.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er DashboardLayout **Étape 2**: CrĂ©er Sidebar avec navigation **Étape 3**: CrĂ©er Header avec user menu **Étape 4**: Tests layout ### Code Snippets **apps/web/src/components/layout/DashboardLayout.tsx**: ```typescript export function DashboardLayout({ children }: { children: React.ReactNode }) { return (
{children}
); } ``` ### Definition of Done - [x] DashboardLayout créé - [x] Sidebar avec navigation - [x] Header avec user menu - [x] Responsive design - [x] Tests layout créés - [x] Code review approuvĂ© --- ## T0104: Add Frontend Dashboard Page ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-017 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0103 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er page Dashboard principale avec statistiques et aperçu. ### Fichiers Ă  CrĂ©er - `apps/web/src/pages/DashboardPage.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er DashboardPage **Étape 2**: Ajouter statistiques **Étape 3**: Ajouter aperçu rĂ©cent **Étape 4**: Tests dashboard ### Code Snippets **apps/web/src/pages/DashboardPage.tsx**: ```typescript export function DashboardPage() { return (

Dashboard

{/* Statistiques et aperçu */}
); } ``` ### Definition of Done - [x] DashboardPage créée - [x] Statistiques affichĂ©es - [x] Aperçu rĂ©cent - [x] Tests dashboard créés - [x] Code review approuvĂ© --- ## T0105: Add Frontend User Profile Page ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-018 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0103 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er page profil utilisateur avec Ă©dition et affichage des informations. ### Fichiers Ă  CrĂ©er - `apps/web/src/pages/ProfilePage.tsx` - `apps/web/src/features/user/components/ProfileForm.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er ProfilePage **Étape 2**: CrĂ©er ProfileForm **Étape 3**: Ajouter upload avatar **Étape 4**: Tests profile ### Code Snippets **apps/web/src/pages/ProfilePage.tsx**: ```typescript export function ProfilePage() { return ( ); } ``` ### Definition of Done - [x] ProfilePage créée - [x] ProfileForm avec validation - [x] Upload avatar fonctionnel - [x] Tests profile créés - [x] Code review approuvĂ© --- ## T0106: Add Frontend Card Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-019 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0094 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Card rĂ©utilisable pour afficher du contenu dans des cartes. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Card.tsx` - `apps/web/src/components/ui/Card.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er composant Card avec variants **Étape 2**: Ajouter CardHeader, CardContent, CardFooter **Étape 3**: Ajouter support dark mode **Étape 4**: Tests Card component ### Code Snippets **apps/web/src/components/ui/Card.tsx**: ```typescript export interface CardProps extends React.HTMLAttributes { variant?: 'default' | 'outlined' | 'elevated'; } export function Card({ variant = 'default', className, ...props }: CardProps) { return (
); } ``` ### Definition of Done - [x] Card component créé - [x] Variants (default, outlined, elevated) - [x] CardHeader, CardContent, CardFooter - [x] Support dark mode - [x] Tests Card créés - [x] Code review approuvĂ© --- ## T0107: Add Frontend Modal Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-020 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Modal rĂ©utilisable avec overlay, fermeture, et gestion du focus. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Modal.tsx` - `apps/web/src/components/ui/Modal.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Modal avec overlay **Étape 2**: Ajouter gestion fermeture (ESC, click outside) **Étape 3**: Ajouter gestion focus trap **Étape 4**: Tests Modal component ### Code Snippets **apps/web/src/components/ui/Modal.tsx**: ```typescript export interface ModalProps { open: boolean; onClose: () => void; children: React.ReactNode; title?: string; } export function Modal({ open, onClose, children, title }: ModalProps) { useEffect(() => { if (open) { // Focus trap logic } }, [open]); if (!open) return null; return (
{title &&

{title}

} {children}
); } ``` ### Definition of Done - [x] Modal component créé - [x] Overlay avec fermeture - [x] Gestion ESC et click outside - [x] Focus trap - [x] Tests Modal créés - [x] Code review approuvĂ© --- ## T0108: Add Frontend Dropdown Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-021 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Dropdown rĂ©utilisable avec menu et gestion du clavier. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Dropdown.tsx` - `apps/web/src/components/ui/Dropdown.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Dropdown avec trigger et menu **Étape 2**: Ajouter gestion clavier (Arrow keys, Enter, Escape) **Étape 3**: Ajouter positionnement automatique **Étape 4**: Tests Dropdown component ### Code Snippets **apps/web/src/components/ui/Dropdown.tsx**: ```typescript export interface DropdownProps { trigger: React.ReactNode; children: React.ReactNode; align?: 'left' | 'right' | 'center'; } export function Dropdown({ trigger, children, align = 'left' }: DropdownProps) { const [open, setOpen] = useState(false); return (
setOpen(!open)}>{trigger}
{open && (
{children}
)}
); } ``` ### Definition of Done - [x] Dropdown component créé - [x] Menu avec positionnement - [x] Gestion clavier - [x] Fermeture automatique - [x] Tests Dropdown créés - [x] Code review approuvĂ© --- ## T0109: Add Frontend Tooltip Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-022 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Tooltip pour afficher des informations au survol. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Tooltip.tsx` - `apps/web/src/components/ui/Tooltip.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Tooltip avec positionnement **Étape 2**: Ajouter dĂ©lai d'affichage **Étape 3**: Ajouter animations **Étape 4**: Tests Tooltip component ### Code Snippets **apps/web/src/components/ui/Tooltip.tsx**: ```typescript export interface TooltipProps { content: string; children: React.ReactNode; position?: 'top' | 'bottom' | 'left' | 'right'; } export function Tooltip({ content, children, position = 'top' }: TooltipProps) { return (
{children}
{content}
); } ``` ### Definition of Done - [x] Tooltip component créé - [x] Positionnement (top, bottom, left, right) - [x] DĂ©lai d'affichage - [x] Animations - [x] Tests Tooltip créés - [x] Code review approuvĂ© --- ## T0110: Add Frontend Dialog Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-023 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0107 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Dialog avancĂ© avec header, body, footer et actions. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Dialog.tsx` - `apps/web/src/components/ui/Dialog.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Dialog avec structure (header, body, footer) **Étape 2**: Ajouter gestion actions (confirm, cancel) **Étape 3**: Ajouter variantes (alert, confirm, info) **Étape 4**: Tests Dialog component ### Code Snippets **apps/web/src/components/ui/Dialog.tsx**: ```typescript export interface DialogProps { open: boolean; onClose: () => void; title?: string; children: React.ReactNode; footer?: React.ReactNode; variant?: 'default' | 'alert' | 'confirm'; } export function Dialog({ open, onClose, title, children, footer, variant = 'default' }: DialogProps) { return ( {title && {title}} {children} {footer && {footer}} ); } ``` ### Definition of Done - [x] Dialog component créé - [x] Structure (header, body, footer) - [x] Variantes (alert, confirm, info) - [x] Actions (confirm, cancel) - [x] Tests Dialog créés - [x] Code review approuvĂ© --- ## T0111: Add Frontend Select Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-024 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0108 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Select avec recherche, multi-select, et groupes d'options. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Select.tsx` - `apps/web/src/components/ui/Select.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Select avec options **Étape 2**: Ajouter recherche/filtre **Étape 3**: Ajouter multi-select **Étape 4**: Tests Select component ### Code Snippets **apps/web/src/components/ui/Select.tsx**: ```typescript export interface SelectOption { value: string; label: string; disabled?: boolean; } export interface SelectProps { options: SelectOption[]; value?: string | string[]; onChange: (value: string | string[]) => void; multiple?: boolean; searchable?: boolean; placeholder?: string; } export function Select({ options, value, onChange, multiple, searchable, placeholder }: SelectProps) { const [search, setSearch] = useState(''); const filteredOptions = searchable ? options.filter(opt => opt.label.toLowerCase().includes(search.toLowerCase())) : options; return (
searchable ? setSearch(e.target.value) : onChange(e.target.value)} />
    {filteredOptions.map(option => (
  • onChange(option.value)}> {option.label}
  • ))}
); } ``` ### Definition of Done - [x] Select component créé - [x] Support single et multi-select - [x] Recherche/filtre - [x] Groupes d'options - [x] Tests Select créés - [x] Code review approuvĂ© --- ## T0112: Add Frontend DatePicker Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-025 **Phase**: 1 **Priority**: medium **Complexity**: high **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0107 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant DatePicker avec calendrier, sĂ©lection de date unique ou range. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/DatePicker.tsx` - `apps/web/src/components/ui/DatePicker.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er calendrier avec navigation **Étape 2**: Ajouter sĂ©lection date unique **Étape 3**: Ajouter sĂ©lection range **Étape 4**: Tests DatePicker component ### Code Snippets **apps/web/src/components/ui/DatePicker.tsx**: ```typescript export interface DatePickerProps { value?: Date | { start: Date; end: Date }; onChange: (date: Date | { start: Date; end: Date }) => void; mode?: 'single' | 'range'; minDate?: Date; maxDate?: Date; } export function DatePicker({ value, onChange, mode = 'single', minDate, maxDate }: DatePickerProps) { const [currentMonth, setCurrentMonth] = useState(new Date()); return (
{/* Calendar header with month navigation */} {/* Calendar grid with days */}
); } ``` ### Definition of Done - [x] DatePicker component créé - [x] Calendrier avec navigation - [x] SĂ©lection date unique - [x] SĂ©lection range - [x] Validation min/max date - [x] Tests DatePicker créés - [x] Code review approuvĂ© --- ## T0113: Add Frontend FileUpload Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-026 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant FileUpload avec drag & drop, preview, et validation. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/FileUpload.tsx` - `apps/web/src/components/ui/FileUpload.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er FileUpload avec input file **Étape 2**: Ajouter drag & drop **Étape 3**: Ajouter preview et validation **Étape 4**: Tests FileUpload component ### Code Snippets **apps/web/src/components/ui/FileUpload.tsx**: ```typescript export interface FileUploadProps { onFileSelect: (files: File[]) => void; accept?: string; multiple?: boolean; maxSize?: number; showPreview?: boolean; } export function FileUpload({ onFileSelect, accept, multiple, maxSize, showPreview }: FileUploadProps) { const [dragActive, setDragActive] = useState(false); const handleDrop = (e: React.DragEvent) => { e.preventDefault(); const files = Array.from(e.dataTransfer.files); onFileSelect(files); }; return (
e.preventDefault()} className={cn('border-2 border-dashed', dragActive && 'border-primary')} >
); } ``` ### Definition of Done - [x] FileUpload component créé - [x] Drag & drop fonctionnel - [x] Preview des fichiers - [x] Validation (type, taille) - [x] Barre de progression - [x] Tests FileUpload créés - [x] Code review approuvĂ© --- ## T0114: Add Frontend FormBuilder Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-027 **Phase**: 1 **Priority**: medium **Complexity**: high **Temps EstimĂ©**: 3h **DĂ©pendances**: T0111 ✅, T0112 ✅, T0113 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant FormBuilder pour crĂ©er des formulaires dynamiques Ă  partir de configuration. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/forms/FormBuilder.tsx` - `apps/web/src/components/forms/FormBuilder.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er FormBuilder avec configuration **Étape 2**: Ajouter support diffĂ©rents types de champs **Étape 3**: Ajouter validation dynamique **Étape 4**: Tests FormBuilder component ### Code Snippets **apps/web/src/components/forms/FormBuilder.tsx**: ```typescript export interface FormField { name: string; type: 'text' | 'email' | 'select' | 'date' | 'file'; label: string; required?: boolean; validation?: (value: any) => string | null; } export interface FormBuilderProps { fields: FormField[]; onSubmit: (data: Record) => void; } export function FormBuilder({ fields, onSubmit }: FormBuilderProps) { const { register, handleSubmit, formState: { errors } } = useForm(); return (
{fields.map(field => (
{/* Render appropriate input based on field.type */}
))}
); } ``` ### Definition of Done - [x] FormBuilder component créé - [x] Support types de champs multiples - [x] Validation dynamique - [x] Gestion erreurs - [x] Tests FormBuilder créés - [x] Code review approuvĂ© --- ## T0115: Add Frontend Form Validation Utilities ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-028 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0114 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er utilitaires de validation de formulaires rĂ©utilisables avec messages d'erreur. ### Fichiers Ă  CrĂ©er - `apps/web/src/utils/validation.ts` - `apps/web/src/utils/validation.test.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonctions de validation **Étape 2**: Ajouter messages d'erreur **Étape 3**: Ajouter validation composĂ©e **Étape 4**: Tests validation utilities ### Code Snippets **apps/web/src/utils/validation.ts**: ```typescript export const validators = { required: (value: any) => { if (!value || (typeof value === 'string' && !value.trim())) { return 'Ce champ est requis'; } return null; }, email: (value: string) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(value)) { return 'Email invalide'; } return null; }, minLength: (min: number) => (value: string) => { if (value.length < min) { return `Minimum ${min} caractĂšres`; } return null; }, }; ``` ### Definition of Done - [x] Validators créés - [x] Messages d'erreur i18n - [x] Validation composĂ©e - [x] Tests validation créés - [x] Code review approuvĂ© --- ## T0116: Add Frontend Breadcrumbs Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-029 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0096 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Breadcrumbs pour navigation hiĂ©rarchique. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/navigation/Breadcrumbs.tsx` - `apps/web/src/components/navigation/Breadcrumbs.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Breadcrumbs avec items **Étape 2**: Ajouter sĂ©parateur automatique **Étape 3**: IntĂ©grer avec React Router **Étape 4**: Tests Breadcrumbs component ### Code Snippets **apps/web/src/components/navigation/Breadcrumbs.tsx**: ```typescript export interface BreadcrumbItem { label: string; href?: string; } export interface BreadcrumbsProps { items: BreadcrumbItem[]; } export function Breadcrumbs({ items }: BreadcrumbsProps) { return ( ); } ``` ### Definition of Done - [x] Breadcrumbs component créé - [x] SĂ©parateur automatique - [x] IntĂ©gration React Router - [x] Support mobile - [x] Tests Breadcrumbs créés - [x] Code review approuvĂ© --- ## T0117: Add Frontend Tabs Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-030 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Tabs avec gestion de l'Ă©tat actif et navigation clavier. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/navigation/Tabs.tsx` - `apps/web/src/components/navigation/Tabs.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Tabs avec liste et contenu **Étape 2**: Ajouter gestion Ă©tat actif **Étape 3**: Ajouter navigation clavier **Étape 4**: Tests Tabs component ### Code Snippets **apps/web/src/components/navigation/Tabs.tsx**: ```typescript export interface TabItem { id: string; label: string; content: React.ReactNode; disabled?: boolean; } export interface TabsProps { items: TabItem[]; defaultActiveId?: string; onChange?: (id: string) => void; } export function Tabs({ items, defaultActiveId, onChange }: TabsProps) { const [activeId, setActiveId] = useState(defaultActiveId || items[0]?.id); return (
{items.map(item => ( ))}
{items.find(item => item.id === activeId)?.content}
); } ``` ### Definition of Done - [x] Tabs component créé - [x] Gestion Ă©tat actif - [x] Navigation clavier - [x] Support disabled tabs - [x] Tests Tabs créés - [x] Code review approuvĂ© --- ## T0118: Add Frontend Pagination Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-031 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Pagination pour navigation entre pages de rĂ©sultats. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/navigation/Pagination.tsx` - `apps/web/src/components/navigation/Pagination.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Pagination avec boutons prĂ©cĂ©dent/suivant **Étape 2**: Ajouter numĂ©ros de pages **Étape 3**: Ajouter ellipsis pour grandes listes **Étape 4**: Tests Pagination component ### Code Snippets **apps/web/src/components/navigation/Pagination.tsx**: ```typescript export interface PaginationProps { currentPage: number; totalPages: number; onPageChange: (page: number) => void; maxVisiblePages?: number; } export function Pagination({ currentPage, totalPages, onPageChange, maxVisiblePages = 5 }: PaginationProps) { const pages = useMemo(() => { // Calculate visible page numbers const start = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2)); const end = Math.min(totalPages, start + maxVisiblePages - 1); return Array.from({ length: end - start + 1 }, (_, i) => start + i); }, [currentPage, totalPages, maxVisiblePages]); return ( ); } ``` ### Definition of Done - [x] Pagination component créé - [x] Navigation prĂ©cĂ©dent/suivant - [x] NumĂ©ros de pages - [x] Ellipsis pour grandes listes - [x] Tests Pagination créés - [x] Code review approuvĂ© --- ## T0119: Add Frontend Search Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-032 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Search avec autocomplete, suggestions, et historique. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/search/Search.tsx` - `apps/web/src/components/search/Search.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Search avec input **Étape 2**: Ajouter autocomplete **Étape 3**: Ajouter suggestions et historique **Étape 4**: Tests Search component ### Code Snippets **apps/web/src/components/search/Search.tsx**: ```typescript export interface SearchResult { id: string; type: 'track' | 'user' | 'playlist'; title: string; subtitle?: string; } export interface SearchProps { onSearch: (query: string) => void; onResultSelect?: (result: SearchResult) => void; placeholder?: string; showSuggestions?: boolean; } export function Search({ onSearch, onResultSelect, placeholder, showSuggestions }: SearchProps) { const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState([]); const handleSearch = useDebounce((q: string) => { onSearch(q); // Fetch suggestions }, 300); return (
{ setQuery(e.target.value); handleSearch(e.target.value); }} placeholder={placeholder} /> {showSuggestions && suggestions.length > 0 && (
{suggestions.map(result => (
onResultSelect?.(result)}> {result.title}
))}
)}
); } ``` ### Definition of Done - [x] Search component créé - [x] Autocomplete fonctionnel - [x] Suggestions dynamiques - [x] Historique de recherche - [x] Debounce pour performance - [x] Tests Search créés - [x] Code review approuvĂ© --- ## T0120: Add Frontend Filters Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-033 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0111 ✅, T0119 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Filters pour filtrer les rĂ©sultats avec plusieurs critĂšres. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/filters/Filters.tsx` - `apps/web/src/components/filters/Filters.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Filters avec plusieurs types de filtres **Étape 2**: Ajouter gestion Ă©tat des filtres **Étape 3**: Ajouter bouton reset **Étape 4**: Tests Filters component ### Code Snippets **apps/web/src/components/filters/Filters.tsx**: ```typescript export interface FilterOption { id: string; label: string; type: 'select' | 'checkbox' | 'range' | 'date'; options?: { value: string; label: string }[]; } export interface FiltersProps { filters: FilterOption[]; values: Record; onChange: (values: Record) => void; onReset?: () => void; } export function Filters({ filters, values, onChange, onReset }: FiltersProps) { const handleFilterChange = (filterId: string, value: any) => { onChange({ ...values, [filterId]: value }); }; return (
{filters.map(filter => (
{/* Render appropriate filter input based on filter.type */}
))} {onReset && ( )}
); } ``` ### Definition of Done - [x] Filters component créé - [x] Support types de filtres multiples - [x] Gestion Ă©tat des filtres - [x] Bouton reset - [x] Tests Filters créés - [x] Code review approuvĂ© --- ## T0121: Add Frontend Table Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-034 **Phase**: 1 **Priority**: high **Complexity**: high **Temps EstimĂ©**: 3h **DĂ©pendances**: T0106 ✅, T0118 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Table avec tri, pagination, sĂ©lection, et actions. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/data/Table.tsx` - `apps/web/src/components/data/Table.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Table avec colonnes configurables **Étape 2**: Ajouter tri par colonnes **Étape 3**: Ajouter sĂ©lection multiple **Étape 4**: Tests Table component ### Code Snippets **apps/web/src/components/data/Table.tsx**: ```typescript export interface TableColumn { key: string; header: string; render?: (row: T) => React.ReactNode; sortable?: boolean; } export interface TableProps { columns: TableColumn[]; data: T[]; onSort?: (column: string, direction: 'asc' | 'desc') => void; onRowClick?: (row: T) => void; selectable?: boolean; } export function Table({ columns, data, onSort, onRowClick, selectable }: TableProps) { const [selectedRows, setSelectedRows] = useState>(new Set()); const [sortColumn, setSortColumn] = useState(null); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc'); return ( {selectable && } {columns.map(column => ( ))} {data.map((row, index) => ( onRowClick?.(row)}> {selectable && ( )} {columns.map(column => ( ))} ))}
column.sortable && handleSort(column.key)} > {column.header} {sortColumn === column.key && {sortDirection === 'asc' ? '↑' : '↓'}}
handleSelect(index, e.target.checked)} /> {column.render ? column.render(row) : (row as any)[column.key]}
); } ``` ### Definition of Done - [x] Table component créé - [x] Colonnes configurables - [x] Tri par colonnes - [x] SĂ©lection multiple - [x] Pagination intĂ©grĂ©e - [x] Tests Table créés - [x] Code review approuvĂ© --- ## T0122: Add Frontend List Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-035 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant List rĂ©utilisable avec items, actions, et variants. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/data/List.tsx` - `apps/web/src/components/data/List.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er List avec items **Étape 2**: Ajouter variants (default, bordered, spaced) **Étape 3**: Ajouter actions sur items **Étape 4**: Tests List component ### Code Snippets **apps/web/src/components/data/List.tsx**: ```typescript export interface ListItem { id: string; content: React.ReactNode; actions?: React.ReactNode; onClick?: () => void; } export interface ListProps { items: ListItem[]; variant?: 'default' | 'bordered' | 'spaced'; emptyMessage?: string; } export function List({ items, variant = 'default', emptyMessage }: ListProps) { if (items.length === 0 && emptyMessage) { return
{emptyMessage}
; } return (
    {items.map(item => (
  • {item.content}
    {item.actions &&
    {item.actions}
    }
  • ))}
); } ``` ### Definition of Done - [x] List component créé - [x] Variants (default, bordered, spaced) - [x] Actions sur items - [x] Message vide - [x] Tests List créés - [x] Code review approuvĂ© --- ## T0123: Add Frontend Grid Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-036 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Grid responsive pour afficher des items en grille. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/data/Grid.tsx` - `apps/web/src/components/data/Grid.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Grid avec colonnes configurables **Étape 2**: Ajouter responsive breakpoints **Étape 3**: Ajouter gap et spacing **Étape 4**: Tests Grid component ### Code Snippets **apps/web/src/components/data/Grid.tsx**: ```typescript export interface GridProps { children: React.ReactNode; columns?: number | { sm?: number; md?: number; lg?: number; xl?: number }; gap?: number; className?: string; } export function Grid({ children, columns = 3, gap = 4, className }: GridProps) { const gridCols = typeof columns === 'number' ? `grid-cols-${columns}` : Object.entries(columns).map(([breakpoint, cols]) => `${breakpoint}:grid-cols-${cols}` ).join(' '); return (
{children}
); } ``` ### Definition of Done - [x] Grid component créé - [x] Colonnes configurables - [x] Responsive breakpoints - [x] Gap et spacing - [x] Tests Grid créés - [x] Code review approuvĂ© --- ## T0124: Add Frontend Charts Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-037 **Phase**: 1 **Priority**: medium **Complexity**: high **Temps EstimĂ©**: 3h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composants Charts (Line, Bar, Pie) pour visualisation de donnĂ©es. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/charts/Chart.tsx` - `apps/web/src/components/charts/LineChart.tsx` - `apps/web/src/components/charts/BarChart.tsx` - `apps/web/src/components/charts/PieChart.tsx` ### ImplĂ©mentation **Étape 1**: IntĂ©grer bibliothĂšque de charts (recharts ou chart.js) **Étape 2**: CrĂ©er composants LineChart, BarChart, PieChart **Étape 3**: Ajouter configuration et options **Étape 4**: Tests Charts components ### Code Snippets **apps/web/src/components/charts/LineChart.tsx**: ```typescript export interface LineChartData { label: string; value: number; } export interface LineChartProps { data: LineChartData[]; xAxisLabel?: string; yAxisLabel?: string; color?: string; } export function LineChart({ data, xAxisLabel, yAxisLabel, color = '#3b82f6' }: LineChartProps) { return (
{/* Chart implementation using recharts or chart.js */}
); } ``` ### Definition of Done - [x] Charts components créés - [x] LineChart, BarChart, PieChart - [x] Configuration et options - [x] Responsive design - [x] Tests Charts créés - [x] Code review approuvĂ© --- ## T0125: Add Frontend Timeline Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-038 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Timeline pour afficher des Ă©vĂ©nements chronologiques. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/data/Timeline.tsx` - `apps/web/src/components/data/Timeline.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Timeline avec items **Étape 2**: Ajouter variantes (vertical, horizontal) **Étape 3**: Ajouter icĂŽnes et dates **Étape 4**: Tests Timeline component ### Code Snippets **apps/web/src/components/data/Timeline.tsx**: ```typescript export interface TimelineItem { id: string; title: string; description?: string; date: Date; icon?: React.ReactNode; } export interface TimelineProps { items: TimelineItem[]; orientation?: 'vertical' | 'horizontal'; } export function Timeline({ items, orientation = 'vertical' }: TimelineProps) { return (
{items.map((item, index) => (
{item.icon &&
{item.icon}
}
{item.title}
{item.description &&
{item.description}
}
{formatDate(item.date)}
))}
); } ``` ### Definition of Done - [x] Timeline component créé - [x] Variantes (vertical, horizontal) - [x] Support icĂŽnes et dates - [x] Responsive design - [x] Tests Timeline créés - [x] Code review approuvĂ© --- ## T0126: Add Frontend Toast/Notification Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-039 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er systĂšme de notifications Toast avec queue, types, et auto-dismiss. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/feedback/Toast.tsx` - `apps/web/src/components/feedback/ToastProvider.tsx` - `apps/web/src/hooks/useToast.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er Toast component **Étape 2**: CrĂ©er ToastProvider avec queue **Étape 3**: CrĂ©er hook useToast **Étape 4**: Tests Toast system ### Code Snippets **apps/web/src/hooks/useToast.ts**: ```typescript export interface Toast { id: string; message: string; type?: 'success' | 'error' | 'warning' | 'info'; duration?: number; } export function useToast() { const addToast = (toast: Omit) => { // Add toast to queue }; return { success: (message: string) => addToast({ message, type: 'success' }), error: (message: string) => addToast({ message, type: 'error' }), warning: (message: string) => addToast({ message, type: 'warning' }), info: (message: string) => addToast({ message, type: 'info' }), }; } ``` ### Definition of Done - [x] Toast component créé - [x] ToastProvider avec queue - [x] Hook useToast - [x] Types (success, error, warning, info) - [x] Auto-dismiss - [x] Tests Toast créés - [x] Code review approuvĂ© --- ## T0127: Add Frontend Alert Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-040 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Alert pour afficher des messages d'information, d'avertissement ou d'erreur. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/feedback/Alert.tsx` - `apps/web/src/components/feedback/Alert.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Alert avec variants **Étape 2**: Ajouter icĂŽnes et fermeture **Étape 3**: Ajouter support actions **Étape 4**: Tests Alert component ### Code Snippets **apps/web/src/components/feedback/Alert.tsx**: ```typescript export interface AlertProps { variant?: 'info' | 'success' | 'warning' | 'error'; title?: string; children: React.ReactNode; onClose?: () => void; dismissible?: boolean; } export function Alert({ variant = 'info', title, children, onClose, dismissible }: AlertProps) { return (
{title &&

{title}

}
{children}
{dismissible && onClose && ( )}
); } ``` ### Definition of Done - [x] Alert component créé - [x] Variants (info, success, warning, error) - [x] Support icĂŽnes - [x] Fermeture optionnelle - [x] Tests Alert créés - [x] Code review approuvĂ© --- ## T0128: Add Frontend Progress Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-041 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Progress pour afficher la progression d'une opĂ©ration. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/feedback/Progress.tsx` - `apps/web/src/components/feedback/Progress.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Progress avec barre de progression **Étape 2**: Ajouter variants (linear, circular) **Étape 3**: Ajouter label et pourcentage **Étape 4**: Tests Progress component ### Code Snippets **apps/web/src/components/feedback/Progress.tsx**: ```typescript export interface ProgressProps { value: number; // 0-100 max?: number; variant?: 'linear' | 'circular'; showLabel?: boolean; label?: string; color?: string; } export function Progress({ value, max = 100, variant = 'linear', showLabel, label, color }: ProgressProps) { const percentage = (value / max) * 100; if (variant === 'circular') { return (
{showLabel && {percentage}%}
); } return (
{(showLabel || label) && (
{label} {showLabel && {percentage}%}
)}
); } ``` ### Definition of Done - [x] Progress component créé - [x] Variants (linear, circular) - [x] Label et pourcentage - [x] Animations - [x] Tests Progress créés - [x] Code review approuvĂ© --- ## T0129: Add Frontend Badge Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-042 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 45min **DĂ©pendances**: T0106 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant Badge pour afficher des labels, compteurs, ou statuts. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/ui/Badge.tsx` - `apps/web/src/components/ui/Badge.test.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er Badge avec variants **Étape 2**: Ajouter support compteur **Étape 3**: Ajouter icĂŽnes **Étape 4**: Tests Badge component ### Code Snippets **apps/web/src/components/ui/Badge.tsx**: ```typescript export interface BadgeProps { children: React.ReactNode; variant?: 'default' | 'primary' | 'success' | 'warning' | 'error'; size?: 'sm' | 'md' | 'lg'; dot?: boolean; count?: number; } export function Badge({ children, variant = 'default', size = 'md', dot, count }: BadgeProps) { return ( {dot && } {children} {count !== undefined && ( ({count}) )} ); } ``` ### Definition of Done - [x] Badge component créé - [x] Variants (default, primary, success, warning, error) - [x] Support compteur - [x] Support dot - [x] Tests Badge créés - [x] Code review approuvĂ© --- ## T0130: Add Frontend Tooltip Advanced Component ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-FRONT-043 **Phase**: 1 **Priority**: low **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0109 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique AmĂ©liorer composant Tooltip avec positionnement avancĂ©, contenu riche, et triggers multiples. ### Fichiers Ă  Modifier - `apps/web/src/components/ui/Tooltip.tsx` ### ImplĂ©mentation **Étape 1**: AmĂ©liorer positionnement (flip, shift) **Étape 2**: Ajouter contenu riche (HTML, React components) **Étape 3**: Ajouter triggers (hover, click, focus) **Étape 4**: Tests Tooltip avancĂ© ### Code Snippets **apps/web/src/components/ui/Tooltip.tsx**: ```typescript export interface TooltipProps { content: React.ReactNode; children: React.ReactNode; position?: 'top' | 'bottom' | 'left' | 'right'; trigger?: 'hover' | 'click' | 'focus'; delay?: number; showArrow?: boolean; maxWidth?: number; } export function Tooltip({ content, children, position = 'top', trigger = 'hover', delay = 200, showArrow = true, maxWidth = 300 }: TooltipProps) { const [visible, setVisible] = useState(false); const timeoutRef = useRef(); const showTooltip = () => { if (delay > 0) { timeoutRef.current = setTimeout(() => setVisible(true), delay); } else { setVisible(true); } }; const hideTooltip = () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } setVisible(false); }; const triggerProps = { hover: { onMouseEnter: showTooltip, onMouseLeave: hideTooltip }, click: { onClick: showTooltip }, focus: { onFocus: showTooltip, onBlur: hideTooltip }, }[trigger]; return (
{children} {visible && (
{showArrow &&
} {content}
)}
); } ``` ### Definition of Done - [x] Tooltip amĂ©liorĂ© - [x] Positionnement avancĂ© (flip, shift) - [x] Contenu riche supportĂ© - [x] Triggers multiples (hover, click, focus) - [x] DĂ©lai configurable - [x] Tests Tooltip avancĂ© créés - [x] Code review approuvĂ© --- ## T0131: Add Docker Compose for Local Development ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-001 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er docker-compose.yml principal pour le dĂ©veloppement local avec tous les services (PostgreSQL, Redis, Backend API, Chat Server, Stream Server, Frontend). ### Fichiers Ă  CrĂ©er - `docker-compose.yml` - `docker-compose.override.yml.example` ### ImplĂ©mentation **Étape 1**: CrĂ©er docker-compose.yml avec services de base **Étape 2**: Ajouter services backend (API, Chat, Stream) **Étape 3**: Ajouter services frontend **Étape 4**: Ajouter volumes et rĂ©seaux ### Code Snippets **docker-compose.yml**: ```yaml version: '3.8' services: postgres: image: postgres:15-alpine environment: POSTGRES_DB: veza_local POSTGRES_USER: veza_user POSTGRES_PASSWORD: veza_password ports: - "5432:5432" volumes: - postgres_data:/var/lib/postgresql/data networks: - veza-network redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data networks: - veza-network backend-api: build: context: ./veza-backend-api dockerfile: Dockerfile ports: - "8080:8080" environment: DATABASE_URL: postgres://veza_user:veza_password@postgres:5432/veza_local?sslmode=disable REDIS_URL: redis://redis:6379 depends_on: - postgres - redis networks: - veza-network chat-server: build: context: ./veza-chat-server dockerfile: Dockerfile ports: - "8081:8081" environment: DATABASE_URL: postgres://veza_user:veza_password@postgres:5432/veza_local?sslmode=disable depends_on: - postgres networks: - veza-network stream-server: build: context: ./veza-stream-server dockerfile: Dockerfile ports: - "8082:8082" networks: - veza-network frontend: build: context: ./apps/web dockerfile: Dockerfile ports: - "3000:3000" environment: VITE_API_URL: http://localhost:8080/api VITE_WS_URL: ws://localhost:8081/ws VITE_STREAM_URL: ws://localhost:8082/stream depends_on: - backend-api - chat-server - stream-server networks: - veza-network volumes: postgres_data: redis_data: networks: veza-network: driver: bridge ``` ### Definition of Done - [x] docker-compose.yml créé - [x] Services PostgreSQL et Redis configurĂ©s - [x] Services backend (API, Chat, Stream) configurĂ©s - [x] Service frontend configurĂ© - [x] Volumes et rĂ©seaux configurĂ©s - [x] Documentation docker-compose ajoutĂ©e - [x] Code review approuvĂ© --- ## T0132: Add Docker Compose for Production ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0131 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er docker-compose.production.yml pour dĂ©ploiement en production avec configurations sĂ©curisĂ©es, health checks, et restart policies. ### Fichiers Ă  CrĂ©er - `docker-compose.production.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er docker-compose.production.yml **Étape 2**: Ajouter health checks pour tous les services **Étape 3**: Configurer restart policies **Étape 4**: Ajouter secrets et variables d'environnement sĂ©curisĂ©es ### Code Snippets **docker-compose.production.yml**: ```yaml version: '3.8' services: postgres: image: postgres:15-alpine environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data networks: - veza-network healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped redis: image: redis:7-alpine command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redis_data:/data networks: - veza-network healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped backend-api: image: veza/backend-api:latest ports: - "8080:8080" environment: DATABASE_URL: ${DATABASE_URL} REDIS_URL: ${REDIS_URL} JWT_SECRET: ${JWT_SECRET} depends_on: postgres: condition: service_healthy redis: condition: service_healthy networks: - veza-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 restart: unless-stopped # ... autres services avec health checks ``` ### Definition of Done - [x] docker-compose.production.yml créé - [x] Health checks configurĂ©s pour tous les services - [x] Restart policies configurĂ©es - [x] Secrets gĂ©rĂ©s via variables d'environnement - [x] Documentation production ajoutĂ©e - [x] Code review approuvĂ© --- ## T0133: Add Docker Compose for Testing ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-003 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0131 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er docker-compose.test.yml pour environnement de test avec bases de donnĂ©es isolĂ©es et configurations de test. ### Fichiers Ă  CrĂ©er - `docker-compose.test.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er docker-compose.test.yml **Étape 2**: Configurer bases de donnĂ©es de test **Étape 3**: Ajouter services de test isolĂ©s **Étape 4**: Configurer cleanup automatique ### Code Snippets **docker-compose.test.yml**: ```yaml version: '3.8' services: postgres-test: image: postgres:15-alpine environment: POSTGRES_DB: veza_test POSTGRES_USER: veza_test POSTGRES_PASSWORD: veza_test ports: - "5434:5432" tmpfs: - /var/lib/postgresql/data networks: - veza-test-network redis-test: image: redis:7-alpine ports: - "6380:6379" tmpfs: - /data networks: - veza-test-network networks: veza-test-network: driver: bridge ``` ### Definition of Done - [x] docker-compose.test.yml créé - [x] Bases de donnĂ©es de test configurĂ©es - [x] Services isolĂ©s pour tests - [x] Cleanup automatique configurĂ© - [x] Documentation test ajoutĂ©e - [x] Code review approuvĂ© --- ## T0134: Add Docker Compose Health Checks ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-004 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0131 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter health checks complets pour tous les services dans docker-compose.yml. ### Fichiers Ă  Modifier - `docker-compose.yml` - `docker-compose.production.yml` ### ImplĂ©mentation **Étape 1**: Ajouter health checks PostgreSQL **Étape 2**: Ajouter health checks Redis **Étape 3**: Ajouter health checks Backend API **Étape 4**: Ajouter health checks Chat Server et Stream Server ### Code Snippets **docker-compose.yml** (extrait): ```yaml services: postgres: healthcheck: test: ["CMD-SHELL", "pg_isready -U veza_user"] interval: 10s timeout: 5s retries: 5 backend-api: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` ### Definition of Done - [x] Health checks ajoutĂ©s pour tous les services - [x] Intervalles et timeouts configurĂ©s - [x] Retry policies dĂ©finies - [x] Documentation health checks ajoutĂ©e - [x] Code review approuvĂ© --- ## T0135: Add Docker Compose Environment Variables ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-005 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0131 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er fichier .env.example et documenter toutes les variables d'environnement nĂ©cessaires pour docker-compose. ### Fichiers Ă  CrĂ©er - `.env.example` - `docker-compose.env.example` ### ImplĂ©mentation **Étape 1**: CrĂ©er .env.example avec toutes les variables **Étape 2**: Documenter chaque variable **Étape 3**: Ajouter validation des variables requises **Étape 4**: CrĂ©er script de validation ### Code Snippets **.env.example**: ```bash # Database POSTGRES_DB=veza_local POSTGRES_USER=veza_user POSTGRES_PASSWORD=veza_password DATABASE_URL=postgres://veza_user:veza_password@postgres:5432/veza_local?sslmode=disable # Redis REDIS_URL=redis://redis:6379 REDIS_PASSWORD= # JWT JWT_SECRET=your-secret-key-here JWT_EXPIRY=24h # API API_PORT=8080 API_ENV=development # Frontend VITE_API_URL=http://localhost:8080/api VITE_WS_URL=ws://localhost:8081/ws VITE_STREAM_URL=ws://localhost:8082/stream ``` ### Definition of Done - [x] .env.example créé - [x] Toutes les variables documentĂ©es - [x] Validation des variables requises - [x] Script de validation créé - [x] Documentation ajoutĂ©e - [x] Code review approuvĂ© --- ## T0136: Optimize Backend API Dockerfile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-006 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er Dockerfile optimisĂ© pour Backend API Go avec multi-stage build, cache layers, et sĂ©curitĂ©. ### Fichiers Ă  CrĂ©er - `veza-backend-api/Dockerfile` - `veza-backend-api/Dockerfile.production` ### ImplĂ©mentation **Étape 1**: CrĂ©er Dockerfile avec multi-stage build **Étape 2**: Optimiser layers de cache **Étape 3**: Ajouter sĂ©curitĂ© (non-root user) **Étape 4**: Optimiser taille de l'image ### Code Snippets **veza-backend-api/Dockerfile**: ```dockerfile # Build stage FROM golang:1.23-alpine AS builder WORKDIR /app # Install build dependencies RUN apk add --no-cache git # Copy go mod files COPY go.mod go.sum ./ RUN go mod download # Copy source code COPY . . # Build RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o veza-api ./cmd/api # Runtime stage FROM alpine:latest RUN apk --no-cache add ca-certificates tzdata WORKDIR /root/ # Copy binary from builder COPY --from=builder /app/veza-api . # Create non-root user RUN addgroup -g 1001 -S app && \ adduser -S app -u 1001 # Change ownership RUN chown -R app:app /root USER app EXPOSE 8080 CMD ["./veza-api"] ``` ### Definition of Done - [x] Dockerfile créé avec multi-stage build - [x] Cache layers optimisĂ©s - [x] Non-root user configurĂ© - [x] Image size optimisĂ©e - [x] Tests Dockerfile passent - [x] Code review approuvĂ© --- ## T0137: Optimize Chat Server Dockerfile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-007 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Optimiser Dockerfile existant pour Chat Server Rust avec cache optimisĂ© pour Cargo. ### Fichiers Ă  Modifier - `veza-chat-server/Dockerfile` ### ImplĂ©mentation **Étape 1**: Optimiser cache Cargo **Étape 2**: Utiliser cargo-chef si possible **Étape 3**: Minimiser taille de l'image finale **Étape 4**: Ajouter sĂ©curitĂ© ### Code Snippets **veza-chat-server/Dockerfile**: ```dockerfile FROM rust:1.75-alpine AS builder WORKDIR /app # Install build dependencies RUN apk add --no-cache musl-dev # Copy Cargo files first for better caching COPY Cargo.toml Cargo.lock ./ RUN cargo fetch # Copy source code COPY src ./src COPY migrations ./migrations # Build release RUN cargo build --release # Runtime stage FROM alpine:latest RUN apk add --no-cache ca-certificates WORKDIR /app COPY --from=builder /app/target/release/chat_server ./ COPY --from=builder /app/migrations ./migrations RUN addgroup -g 1001 -S app && \ adduser -S app -u 1001 && \ chown -R app:app /app USER app EXPOSE 8081 CMD ["./chat_server"] ``` ### Definition of Done - [x] Dockerfile optimisĂ© avec cache Cargo - [x] Taille de l'image minimisĂ©e - [x] Non-root user configurĂ© - [x] Tests Dockerfile passent - [x] Code review approuvĂ© --- ## T0138: Optimize Stream Server Dockerfile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-008 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Optimiser Dockerfile existant pour Stream Server Rust avec cache optimisĂ©. ### Fichiers Ă  Modifier - `veza-stream-server/Dockerfile` ### ImplĂ©mentation **Étape 1**: Optimiser cache Cargo **Étape 2**: Minimiser dĂ©pendances runtime **Étape 3**: Optimiser taille de l'image **Étape 4**: Ajouter sĂ©curitĂ© ### Definition of Done - [x] Dockerfile optimisĂ© - [x] Cache Cargo optimisĂ© - [x] Image size minimisĂ©e - [x] Tests Dockerfile passent - [x] Code review approuvĂ© --- ## T0139: Optimize Frontend Dockerfile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-009 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er Dockerfile optimisĂ© pour Frontend React avec multi-stage build (build + nginx). ### Fichiers Ă  CrĂ©er - `apps/web/Dockerfile` - `apps/web/Dockerfile.dev` - `apps/web/nginx.conf` ### ImplĂ©mentation **Étape 1**: CrĂ©er Dockerfile avec build stage **Étape 2**: CrĂ©er nginx stage pour production **Étape 3**: Configurer nginx pour SPA **Étape 4**: Optimiser cache npm ### Code Snippets **apps/web/Dockerfile**: ```dockerfile # Build stage FROM node:20-alpine AS builder WORKDIR /app # Copy package files COPY package*.json ./ RUN npm ci # Copy source COPY . . # Build RUN npm run build # Production stage FROM nginx:alpine COPY --from=builder /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] ``` ### Definition of Done - [x] Dockerfile créé avec multi-stage build - [x] Nginx configurĂ© pour SPA - [x] Cache npm optimisĂ© - [x] Tests Dockerfile passent - [x] Code review approuvĂ© --- ## T0140: Add .dockerignore Files ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-INFRA-010 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX **Note**: Les fichiers .dockerignore ont Ă©tĂ© créés lors des tĂąches prĂ©cĂ©dentes (T0136, T0137, T0138, T0139). ### Description Technique CrĂ©er fichiers .dockerignore pour tous les services pour optimiser le contexte de build Docker. ### Fichiers Ă  CrĂ©er - `veza-backend-api/.dockerignore` - `veza-chat-server/.dockerignore` - `veza-stream-server/.dockerignore` - `apps/web/.dockerignore` ### ImplĂ©mentation **Étape 1**: CrĂ©er .dockerignore pour Backend API **Étape 2**: CrĂ©er .dockerignore pour Chat Server **Étape 3**: CrĂ©er .dockerignore pour Stream Server **Étape 4**: CrĂ©er .dockerignore pour Frontend ### Code Snippets **.dockerignore** (exemple): ``` node_modules npm-debug.log .git .gitignore .env .env.local dist build coverage *.test.js *.test.ts .DS_Store ``` ### Definition of Done - [x] .dockerignore créé pour tous les services - [x] Fichiers inutiles exclus - [x] Build context optimisĂ© - [x] Code review approuvĂ© --- ## T0141: Add GitHub Actions CI Pipeline ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-CICD-001 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 3h **DĂ©pendances**: Aucune **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er pipeline CI GitHub Actions pour tests automatiques, linting, et build sur chaque PR. ### Fichiers Ă  CrĂ©er - `.github/workflows/ci.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er workflow CI pour Backend Go **Étape 2**: CrĂ©er workflow CI pour Rust services **Étape 3**: CrĂ©er workflow CI pour Frontend **Étape 4**: Ajouter matrix builds et caching ### Code Snippets **.github/workflows/ci.yml**: ```yaml name: CI on: push: branches: [main, develop] pull_request: branches: [main, develop] jobs: backend-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: go-version: '1.23' - name: Cache Go modules uses: actions/cache@v3 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Run tests run: | cd veza-backend-api go test ./... -v -coverprofile=coverage.out rust-test: runs-on: ubuntu-latest strategy: matrix: service: [chat-server, stream-server] steps: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: toolchain: stable - name: Cache Cargo uses: actions/cache@v3 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Run tests run: | cd veza-${{ matrix.service }} cargo test --all-features frontend-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: Cache node modules uses: actions/cache@v3 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - name: Install dependencies run: | cd apps/web npm ci - name: Run tests run: | cd apps/web npm test - name: Build run: | cd apps/web npm run build ``` ### Definition of Done - [x] Workflow CI créé - [x] Tests automatiques configurĂ©s - [x] Linting configurĂ© - [x] Build vĂ©rifiĂ© - [x] Caching configurĂ© - [x] Code review approuvĂ© --- ## T0142: Add GitHub Actions CD Pipeline ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-CICD-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 3h **DĂ©pendances**: T0141 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er pipeline CD GitHub Actions pour build et push d'images Docker, et dĂ©ploiement automatique. ### Fichiers Ă  CrĂ©er - `.github/workflows/cd.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er workflow CD pour build images **Étape 2**: Configurer push vers Docker Hub/Registry **Étape 3**: Ajouter dĂ©ploiement staging **Étape 4**: Ajouter dĂ©ploiement production (manuel) ### Code Snippets **.github/workflows/cd.yml**: ```yaml name: CD on: push: branches: [main] tags: - 'v*' jobs: build-and-push: runs-on: ubuntu-latest strategy: matrix: service: [backend-api, chat-server, stream-server, frontend] steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push uses: docker/build-push-action@v5 with: context: ./veza-${{ matrix.service }} push: ${{ github.event_name != 'pull_request' }} tags: veza/${{ matrix.service }}:latest ``` ### Definition of Done - [x] Workflow CD créé - [x] Build et push images configurĂ©s - [x] DĂ©ploiement staging configurĂ© - [x] DĂ©ploiement production configurĂ© - [x] Secrets configurĂ©s - [x] Code review approuvĂ© --- ## T0143: Add GitHub Actions Lint Pipeline ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-CICD-003 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0141 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er workflow GitHub Actions pour linting automatique (Go, Rust, TypeScript). ### Fichiers Ă  CrĂ©er - `.github/workflows/lint.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er workflow lint pour Go **Étape 2**: CrĂ©er workflow lint pour Rust **Étape 3**: CrĂ©er workflow lint pour TypeScript **Étape 4**: Ajouter format checking ### Code Snippets **.github/workflows/lint.yml**: ```yaml name: Lint on: [push, pull_request] jobs: lint-go: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: go-version: '1.23' - name: Run golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest lint-rust: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: toolchain: stable - name: Run clippy run: | cd veza-chat-server cargo clippy -- -D warnings lint-typescript: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' - name: Run ESLint run: | cd apps/web npm ci npm run lint ``` ### Definition of Done - [x] Workflow lint créé - [x] Linting Go configurĂ© - [x] Linting Rust configurĂ© - [x] Linting TypeScript configurĂ© - [x] Format checking configurĂ© - [x] Code review approuvĂ© --- ## T0144: Add GitHub Actions Security Scan ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-CICD-004 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0141 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er workflow GitHub Actions pour scans de sĂ©curitĂ© (dĂ©pendances, images Docker, code). ### Fichiers Ă  CrĂ©er - `.github/workflows/security.yml` ### ImplĂ©mentation **Étape 1**: Ajouter scan de dĂ©pendances (npm audit, go mod, cargo audit) **Étape 2**: Ajouter scan d'images Docker (Trivy) **Étape 3**: Ajouter scan de code (CodeQL) **Étape 4**: Configurer alerts ### Code Snippets **.github/workflows/security.yml**: ```yaml name: Security Scan on: [push, pull_request] jobs: dependency-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Scan npm dependencies run: | cd apps/web npm audit --audit-level=moderate - name: Scan Go dependencies run: | cd veza-backend-api go list -json -m all | nancy sleuth docker-scan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run Trivy vulnerability scanner uses: aquasecurity/trivy-action@master with: scan-type: 'fs' scan-ref: '.' format: 'sarif' output: 'trivy-results.sarif' - name: Upload Trivy results uses: github/codeql-action/upload-sarif@v2 with: sarif_file: 'trivy-results.sarif' ``` ### Definition of Done - [x] Workflow security créé - [x] Scan dĂ©pendances configurĂ© - [x] Scan Docker configurĂ© - [x] Scan code configurĂ© - [x] Alerts configurĂ©s - [x] Code review approuvĂ© --- ## T0145: Add GitHub Actions Release Workflow ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-CICD-005 **Phase**: 1 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0142 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er workflow GitHub Actions pour releases automatiques (tags, changelog, GitHub releases). ### Fichiers Ă  CrĂ©er - `.github/workflows/release.yml` ### ImplĂ©mentation **Étape 1**: CrĂ©er workflow release sur tag **Étape 2**: GĂ©nĂ©rer changelog automatique **Étape 3**: CrĂ©er GitHub release **Étape 4**: Build et push images avec tags ### Code Snippets **.github/workflows/release.yml**: ```yaml name: Release on: push: tags: - 'v*.*.*' jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Generate changelog uses: metcalfc/changelog-generator@v4 - name: Create Release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: Release ${{ github.ref }} body_path: CHANGELOG.md draft: false prerelease: false ``` ### Definition of Done - [x] Workflow release créé - [x] GĂ©nĂ©ration changelog automatique - [x] GitHub release automatique - [x] Images Docker taguĂ©es - [x] Documentation release ajoutĂ©e - [x] Code review approuvĂ© --- ## T0146: Add Deployment Script for Local Development ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-DEPLOY-001 **Phase**: 1 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0131 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er script de dĂ©ploiement local pour dĂ©marrer tous les services avec docker-compose. ### Fichiers Ă  CrĂ©er - `scripts/deploy-local.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er script deploy-local.sh **Étape 2**: Ajouter vĂ©rification prĂ©requis **Étape 3**: Ajouter build et dĂ©marrage services **Étape 4**: Ajouter health checks ### Code Snippets **scripts/deploy-local.sh**: ```bash #!/bin/bash set -e echo "🚀 Starting Veza local development environment..." # Check prerequisites command -v docker >/dev/null 2>&1 || { echo "Docker is required but not installed. Aborting." >&2; exit 1; } command -v docker-compose >/dev/null 2>&1 || { echo "Docker Compose is required but not installed. Aborting." >&2; exit 1; } # Copy .env.example if .env doesn't exist if [ ! -f .env ]; then echo "📝 Creating .env file from .env.example..." cp .env.example .env fi # Build and start services echo "🔹 Building and starting services..." docker-compose up -d --build echo "✅ Services started successfully!" echo "📊 Health checks in progress..." sleep 10 # Check health docker-compose ps ``` ### Definition of Done - [x] Script deploy-local.sh créé - [x] VĂ©rification prĂ©requis ajoutĂ©e - [x] Build et dĂ©marrage configurĂ©s - [x] Health checks ajoutĂ©s - [x] Script exĂ©cutable (chmod +x) - [x] Code review approuvĂ© --- ## T0147: Add Deployment Script for Production ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-DEPLOY-002 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0132 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er script de dĂ©ploiement production avec rollback, health checks, et sauvegarde. ### Fichiers Ă  CrĂ©er - `scripts/deploy-production.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er script deploy-production.sh **Étape 2**: Ajouter backup avant dĂ©ploiement **Étape 3**: Ajouter dĂ©ploiement avec rollback **Étape 4**: Ajouter vĂ©rifications post-dĂ©ploiement ### Code Snippets **scripts/deploy-production.sh**: ```bash #!/bin/bash set -e ENVIRONMENT=${1:-production} BACKUP_DIR="./backups/$(date +%Y%m%d_%H%M%S)" echo "🚀 Deploying to ${ENVIRONMENT}..." # Create backup echo "📩 Creating backup..." mkdir -p "${BACKUP_DIR}" docker-compose -f docker-compose.production.yml exec postgres pg_dump -U veza_user veza_db > "${BACKUP_DIR}/database.sql" # Pull latest images echo "âŹ‡ïž Pulling latest images..." docker-compose -f docker-compose.production.yml pull # Deploy with zero downtime echo "🔄 Deploying services..." docker-compose -f docker-compose.production.yml up -d --no-deps --build # Health checks echo "đŸ„ Waiting for health checks..." sleep 30 # Verify deployment if docker-compose -f docker-compose.production.yml ps | grep -q "unhealthy"; then echo "❌ Deployment failed! Rolling back..." # Rollback logic exit 1 fi echo "✅ Deployment successful!" ``` ### Definition of Done - [x] Script deploy-production.sh créé - [x] Backup avant dĂ©ploiement - [x] Rollback automatique configurĂ© - [x] Health checks post-dĂ©ploiement - [x] Script exĂ©cutable - [x] Code review approuvĂ© --- ## T0148: Add Database Migration Script ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-DEPLOY-003 **Phase**: 1 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0131 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er script pour exĂ©cuter les migrations de base de donnĂ©es de maniĂšre sĂ©curisĂ©e. ### Fichiers Ă  CrĂ©er - `scripts/migrate-db.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er script migrate-db.sh **Étape 2**: Ajouter vĂ©rification des migrations **Étape 3**: Ajouter backup avant migration **Étape 4**: Ajouter rollback en cas d'erreur ### Code Snippets **scripts/migrate-db.sh**: ```bash #!/bin/bash set -e ENVIRONMENT=${1:-local} COMPOSE_FILE=${ENVIRONMENT == "production" ? "docker-compose.production.yml" : "docker-compose.yml"} echo "🔄 Running database migrations for ${ENVIRONMENT}..." # Backup database echo "📩 Creating backup..." docker-compose -f "${COMPOSE_FILE}" exec -T postgres pg_dump -U veza_user veza_db > "backup_$(date +%Y%m%d_%H%M%S).sql" # Run migrations echo "📝 Running migrations..." docker-compose -f "${COMPOSE_FILE}" exec -T backend-api ./migrate up echo "✅ Migrations completed successfully!" ``` ### Definition of Done - [x] Script migrate-db.sh créé - [x] VĂ©rification migrations configurĂ©e - [x] Backup avant migration - [x] Rollback en cas d'erreur - [x] Script exĂ©cutable - [x] Code review approuvĂ© --- ## T0149: Add Health Check Script ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-DEPLOY-004 **Phase**: 1 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0134 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er script pour vĂ©rifier la santĂ© de tous les services dĂ©ployĂ©s. ### Fichiers Ă  CrĂ©er - `scripts/health-check.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er script health-check.sh **Étape 2**: VĂ©rifier health de tous les services **Étape 3**: Afficher statut dĂ©taillĂ© **Étape 4**: Retourner code d'erreur si Ă©chec ### Code Snippets **scripts/health-check.sh**: ```bash #!/bin/bash set -e ENVIRONMENT=${1:-local} COMPOSE_FILE=${ENVIRONMENT == "production" ? "docker-compose.production.yml" : "docker-compose.yml"} echo "đŸ„ Checking health of all services..." # Check each service services=("postgres" "redis" "backend-api" "chat-server" "stream-server" "frontend") for service in "${services[@]}"; do if docker-compose -f "${COMPOSE_FILE}" ps "${service}" | grep -q "healthy\|running"; then echo "✅ ${service} is healthy" else echo "❌ ${service} is not healthy" exit 1 fi done echo "✅ All services are healthy!" ``` ### Definition of Done - [x] Script health-check.sh créé - [x] VĂ©rification de tous les services - [x] Affichage statut dĂ©taillĂ© - [x] Code d'erreur appropriĂ© - [x] Script exĂ©cutable - [x] Code review approuvĂ© --- ## T0150: Add Logs Collection Script ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-DEPLOY-005 **Phase**: 1 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0131 **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er script pour collecter et afficher les logs de tous les services. ### Fichiers Ă  CrĂ©er - `scripts/logs.sh` ### ImplĂ©mentation **Étape 1**: CrĂ©er script logs.sh **Étape 2**: Ajouter options pour logs suivis **Étape 3**: Ajouter filtrage par service **Étape 4**: Ajouter export logs vers fichier ### Code Snippets **scripts/logs.sh**: ```bash #!/bin/bash ENVIRONMENT=${1:-local} SERVICE=${2:-} FOLLOW=${3:-} COMPOSE_FILE=${ENVIRONMENT == "production" ? "docker-compose.production.yml" : "docker-compose.yml"} if [ -n "${SERVICE}" ]; then docker-compose -f "${COMPOSE_FILE}" logs ${FOLLOW} "${SERVICE}" else docker-compose -f "${COMPOSE_FILE}" logs ${FOLLOW} fi ``` ### Definition of Done - [x] Script logs.sh créé - [x] Options logs suivis ajoutĂ©es - [x] Filtrage par service - [x] Export logs vers fichier - [x] Script exĂ©cutable - [x] Code review approuvĂ© --- ## T0151: Create User Registration Endpoint ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0006 ✅, T0014 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST `/api/v1/auth/register` pour l'inscription utilisateur. Valider email et password, crĂ©er utilisateur en base, gĂ©nĂ©rer JWT et refresh token. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/auth_handler.go` - `veza-backend-api/internal/handlers/auth_handler_test.go` - `veza-backend-api/internal/dto/register_request.go` ### Fichiers Ă  Modifier - `veza-backend-api/cmd/api/main.go` (ajouter routes) ### ImplĂ©mentation **Étape 1**: CrĂ©er DTO RegisterRequest avec validation **Étape 2**: CrĂ©er handler Register avec validation email/password **Étape 3**: CrĂ©er utilisateur en base avec password hashĂ© **Étape 4**: GĂ©nĂ©rer JWT et refresh token **Étape 5**: Retourner response avec user et tokens ### Code Snippets **veza-backend-api/internal/dto/register_request.go**: ```go package dto type RegisterRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required,min=12"` PasswordConfirm string `json:"password_confirm" binding:"required,eqfield=Password"` } type RegisterResponse struct { User UserResponse `json:"user"` Token TokenResponse `json:"token"` } type UserResponse struct { ID uint `json:"id"` Email string `json:"email"` } type TokenResponse struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` } ``` **veza-backend-api/internal/handlers/auth_handler.go**: ```go package handlers import ( "net/http" "github.com/gin-gonic/gin" "veza/internal/dto" "veza/internal/services" ) type AuthHandler struct { authService *services.AuthService } func NewAuthHandler(authService *services.AuthService) *AuthHandler { return &AuthHandler{authService: authService} } func (h *AuthHandler) Register(c *gin.Context) { var req dto.RegisterRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, tokens, err := h.authService.Register(req.Email, req.Password) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response := dto.RegisterResponse{ User: dto.UserResponse{ ID: user.ID, Email: user.Email, }, Token: dto.TokenResponse{ AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, ExpiresIn: 900, // 15 minutes }, } c.JSON(http.StatusCreated, response) } ``` ### Tests Ă  Écrire **Integration Tests**: ```go func TestRegister_Success(t *testing.T) { // Setup router := setupTestRouter() // Test payload := dto.RegisterRequest{ Email: "test@example.com", Password: "SecurePass123!", PasswordConfirm: "SecurePass123!", } w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth/register", jsonBody(payload)) router.ServeHTTP(w, req) // Assert assert.Equal(t, http.StatusCreated, w.Code) var response dto.RegisterResponse json.Unmarshal(w.Body.Bytes(), &response) assert.NotEmpty(t, response.Token.AccessToken) } ``` ### Definition of Done - [x] Endpoint POST /api/v1/auth/register créé - [x] Validation email et password implĂ©mentĂ©e - [x] Utilisateur créé en base avec password hashĂ© - [x] JWT et refresh token gĂ©nĂ©rĂ©s - [x] Tests unitaires (coverage ≄ 80%) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© - [x] Documentation API mise Ă  jour - [x] DĂ©ployĂ© en staging --- ## T0152: Implement Email Validation ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0151 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter validation email RFC 5322 avec vĂ©rification format, domaines valides, et unicitĂ© en base. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/validators/email_validator.go` - `veza-backend-api/internal/validators/email_validator_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er EmailValidator avec regex RFC 5322 **Étape 2**: VĂ©rifier format email valide **Étape 3**: VĂ©rifier domaine email (MX record optionnel) **Étape 4**: VĂ©rifier unicitĂ© email en base ### Code Snippets **veza-backend-api/internal/validators/email_validator.go**: ```go package validators import ( "regexp" "strings" "gorm.io/gorm" ) var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) type EmailValidator struct { db *gorm.DB } func NewEmailValidator(db *gorm.DB) *EmailValidator { return &EmailValidator{db: db} } func (v *EmailValidator) ValidateFormat(email string) bool { email = strings.ToLower(strings.TrimSpace(email)) if len(email) > 254 { return false } return emailRegex.MatchString(email) } func (v *EmailValidator) IsUnique(email string) (bool, error) { var count int64 err := v.db.Model(&models.User{}). Where("LOWER(email) = LOWER(?)", email). Count(&count).Error if err != nil { return false, err } return count == 0, nil } func (v *EmailValidator) Validate(email string) error { if !v.ValidateFormat(email) { return errors.New("invalid email format") } unique, err := v.IsUnique(email) if err != nil { return err } if !unique { return errors.New("email already exists") } return nil } ``` ### Definition of Done - [x] EmailValidator créé avec validation RFC 5322 - [x] VĂ©rification format email - [x] VĂ©rification unicitĂ© email - [x] Tests unitaires (coverage ≄ 80%) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© --- ## T0153: Implement Password Strength Validation ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0151 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter validation force mot de passe avec rĂšgles: min 12 caractĂšres, majuscule, minuscule, chiffre, caractĂšre spĂ©cial. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/validators/password_validator.go` - `veza-backend-api/internal/validators/password_validator_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er PasswordValidator avec rĂšgles de force **Étape 2**: VĂ©rifier longueur minimale (12 caractĂšres) **Étape 3**: VĂ©rifier prĂ©sence majuscule, minuscule, chiffre **Étape 4**: VĂ©rifier prĂ©sence caractĂšre spĂ©cial ### Code Snippets **veza-backend-api/internal/validators/password_validator.go**: ```go package validators import ( "regexp" "unicode" ) var ( hasUpper = regexp.MustCompile(`[A-Z]`) hasLower = regexp.MustCompile(`[a-z]`) hasNumber = regexp.MustCompile(`[0-9]`) hasSpecial = regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`) ) type PasswordValidator struct { MinLength int } func NewPasswordValidator() *PasswordValidator { return &PasswordValidator{MinLength: 12} } type PasswordStrength struct { Valid bool Score int Details []string } func (v *PasswordValidator) Validate(password string) (PasswordStrength, error) { strength := PasswordStrength{ Valid: true, Details: []string{}, } // Length check if len(password) < v.MinLength { strength.Valid = false strength.Details = append(strength.Details, "Password must be at least 12 characters long") return strength, nil } // Upper case check if !hasUpper.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain uppercase letter") } else { strength.Score++ } // Lower case check if !hasLower.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain lowercase letter") } else { strength.Score++ } // Number check if !hasNumber.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain number") } else { strength.Score++ } // Special character check if !hasSpecial.MatchString(password) { strength.Valid = false strength.Details = append(strength.Details, "Must contain special character") } else { strength.Score++ } return strength, nil } ``` ### Definition of Done - [x] PasswordValidator créé avec rĂšgles de force - [x] VĂ©rification longueur minimale - [x] VĂ©rification majuscule, minuscule, chiffre, spĂ©cial - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0154: Implement Password Hashing Service ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0151 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter service de hachage password avec bcrypt cost 12 pour sĂ©curitĂ© optimale. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/password_service.go` - `veza-backend-api/internal/services/password_service_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er PasswordService avec Hash et Compare **Étape 2**: Utiliser bcrypt avec cost 12 **Étape 3**: ImplĂ©menter Hash pour crĂ©er hash **Étape 4**: ImplĂ©menter Compare pour vĂ©rifier password ### Code Snippets **veza-backend-api/internal/services/password_service.go**: ```go package services import ( "golang.org/x/crypto/bcrypt" ) const bcryptCost = 12 type PasswordService struct{} func NewPasswordService() *PasswordService { return &PasswordService{} } func (s *PasswordService) Hash(password string) (string, error) { bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost) if err != nil { return "", err } return string(bytes), nil } func (s *PasswordService) Compare(hashedPassword, password string) bool { err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) return err == nil } ``` ### Definition of Done - [x] PasswordService créé avec bcrypt - [x] Hash implĂ©mentĂ© avec cost 12 - [x] Compare implĂ©mentĂ© - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0155: Implement User Registration Service ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0151 ✅, T0152 ✅, T0153 ✅, T0154 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service d'inscription utilisateur qui orchestre validation, crĂ©ation utilisateur, et gĂ©nĂ©ration tokens. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/auth_service.go` - `veza-backend-api/internal/services/auth_service_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/auth_handler.go` (utiliser service) ### ImplĂ©mentation **Étape 1**: CrĂ©er AuthService avec dĂ©pendances **Étape 2**: ImplĂ©menter Register avec validation email/password **Étape 3**: Hasher password et crĂ©er utilisateur **Étape 4**: GĂ©nĂ©rer JWT et refresh token **Étape 5**: Retourner user et tokens ### Code Snippets **veza-backend-api/internal/services/auth_service.go**: ```go package services import ( "errors" "gorm.io/gorm" "veza/internal/models" "veza/internal/validators" ) type AuthService struct { db *gorm.DB emailValidator *validators.EmailValidator passwordValidator *validators.PasswordValidator passwordService *PasswordService jwtService *JWTService } func NewAuthService( db *gorm.DB, emailValidator *validators.EmailValidator, passwordValidator *validators.PasswordValidator, passwordService *PasswordService, jwtService *JWTService, ) *AuthService { return &AuthService{ db: db, emailValidator: emailValidator, passwordValidator: passwordValidator, passwordService: passwordService, jwtService: jwtService, } } type RegisterResult struct { User *models.User Tokens *TokenPair } func (s *AuthService) Register(email, password string) (*models.User, *TokenPair, error) { // Validate email if err := s.emailValidator.Validate(email); err != nil { return nil, nil, err } // Validate password strength, err := s.passwordValidator.Validate(password) if err != nil { return nil, nil, err } if !strength.Valid { return nil, nil, errors.New("password does not meet requirements") } // Hash password hashedPassword, err := s.passwordService.Hash(password) if err != nil { return nil, nil, err } // Create user user := &models.User{ Email: email, PasswordHash: hashedPassword, } if err := s.db.Create(user).Error; err != nil { return nil, nil, err } // Generate tokens tokens, err := s.jwtService.GenerateTokenPair(user.ID, user.Email) if err != nil { return nil, nil, err } return user, tokens, nil } ``` ### Definition of Done - [x] AuthService créé avec toutes dĂ©pendances - [x] Register implĂ©mentĂ© avec validation complĂšte - [x] Utilisateur créé en base - [x] Tokens gĂ©nĂ©rĂ©s - [x] Tests unitaires (coverage ≄ 80%) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© --- ## T0156: Create Registration Form Component ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0101 ✅, T0111 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant formulaire d'inscription avec champs email, password, password confirmation, et validation cĂŽtĂ© client. ### Fichiers Ă  CrĂ©er - `apps/web/src/pages/auth/Register.tsx` - `apps/web/src/pages/auth/Register.test.tsx` - `apps/web/src/components/forms/RegisterForm.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er composant RegisterForm avec champs email, password, passwordConfirm **Étape 2**: Ajouter validation Zod schema **Étape 3**: Ajouter gestion Ă©tat formulaire **Étape 4**: Ajouter gestion erreurs ### Code Snippets **apps/web/src/components/forms/RegisterForm.tsx**: ```typescript import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; const registerSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(12, 'Password must be at least 12 characters'), passwordConfirm: z.string(), }).refine((data) => data.password === data.passwordConfirm, { message: "Passwords don't match", path: ['passwordConfirm'], }); type RegisterFormData = z.infer; export function RegisterForm({ onSubmit }: { onSubmit: (data: RegisterFormData) => Promise }) { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(registerSchema), }); const [isLoading, setIsLoading] = useState(false); const handleFormSubmit = async (data: RegisterFormData) => { setIsLoading(true); try { await onSubmit(data); } finally { setIsLoading(false); } }; return (
); } ``` ### Definition of Done - [x] RegisterForm component créé - [x] Validation Zod schema implĂ©mentĂ©e - [x] Gestion Ă©tat formulaire - [x] Gestion erreurs - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0157: Add Email Validation in Frontend ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0156 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter validation email en temps rĂ©el dans le formulaire d'inscription avec feedback visuel. ### Fichiers Ă  Modifier - `apps/web/src/components/forms/RegisterForm.tsx` ### ImplĂ©mentation **Étape 1**: Ajouter validation email en temps rĂ©el **Étape 2**: Ajouter indicateur visuel email valide/invalide **Étape 3**: Ajouter message d'erreur spĂ©cifique ### Code Snippets **apps/web/src/utils/validation.ts**: ```typescript export function validateEmail(email: string): { valid: boolean; message?: string } { const emailRegex = /^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$/; if (!email) { return { valid: false, message: 'Email is required' }; } if (email.length > 254) { return { valid: false, message: 'Email is too long' }; } if (!emailRegex.test(email)) { return { valid: false, message: 'Invalid email format' }; } return { valid: true }; } ``` ### Definition of Done - [x] Validation email en temps rĂ©el ajoutĂ©e - [x] Indicateur visuel email valide/invalide - [x] Message d'erreur spĂ©cifique - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0158: Add Password Strength Indicator ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0156 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter indicateur de force du mot de passe avec score visuel et rĂšgles de validation. ### Fichiers Ă  CrĂ©er - `apps/web/src/components/forms/PasswordStrengthIndicator.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er composant PasswordStrengthIndicator **Étape 2**: Calculer score de force (0-4) **Étape 3**: Afficher barre de progression visuelle **Étape 4**: Afficher rĂšgles de validation ### Code Snippets **apps/web/src/components/forms/PasswordStrengthIndicator.tsx**: ```typescript import { useMemo } from 'react'; interface PasswordStrengthIndicatorProps { password: string; } export function PasswordStrengthIndicator({ password }: PasswordStrengthIndicatorProps) { const strength = useMemo(() => { let score = 0; const checks = { length: password.length >= 12, upper: /[A-Z]/.test(password), lower: /[a-z]/.test(password), number: /[0-9]/.test(password), special: /[!@#$%^&*(),.?":{}|<>]/.test(password), }; if (checks.length) score++; if (checks.upper) score++; if (checks.lower) score++; if (checks.number) score++; if (checks.special) score++; return { score, checks }; }, [password]); const strengthLabels = ['Very Weak', 'Weak', 'Fair', 'Good', 'Strong']; const strengthColors = ['bg-red-500', 'bg-orange-500', 'bg-yellow-500', 'bg-blue-500', 'bg-green-500']; if (!password) return null; return (
{strengthLabels[strength.score - 1] || 'Very Weak'}
  • {strength.checks.length ? '✓' : '○'} At least 12 characters
  • {strength.checks.upper ? '✓' : '○'} One uppercase letter
  • {strength.checks.lower ? '✓' : '○'} One lowercase letter
  • {strength.checks.number ? '✓' : '○'} One number
  • {strength.checks.special ? '✓' : '○'} One special character
); } ``` ### Definition of Done - [x] PasswordStrengthIndicator component créé - [x] Score de force calculĂ© - [x] Barre de progression visuelle - [x] RĂšgles de validation affichĂ©es - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0159: Add Registration API Integration ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0156 ✅, T0151 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service API pour intĂ©grer l'inscription avec le backend et gĂ©rer les tokens. ### Fichiers Ă  CrĂ©er - `apps/web/src/services/api/auth.ts` - `apps/web/src/services/api/auth.test.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction register dans auth service **Étape 2**: Appeler endpoint POST /api/v1/auth/register **Étape 3**: GĂ©rer tokens dans response **Étape 4**: GĂ©rer erreurs API ### Code Snippets **apps/web/src/services/api/auth.ts**: ```typescript import { apiClient } from './client'; export interface RegisterRequest { email: string; password: string; password_confirm: string; } export interface RegisterResponse { user: { id: number; email: string; }; token: { access_token: string; refresh_token: string; expires_in: number; }; } export async function register(data: RegisterRequest): Promise { const response = await apiClient.post('/auth/register', data); return response.data; } ``` ### Definition of Done - [x] Service register créé - [x] Appel API implĂ©mentĂ© - [x] Gestion tokens - [x] Gestion erreurs - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0160: Add Registration Success Flow ✅ **Feature Parente**: FEAT-AUTH-001 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0159 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter flow de succĂšs aprĂšs inscription avec stockage tokens et redirection vers dashboard. ### Fichiers Ă  Modifier - `apps/web/src/pages/auth/Register.tsx` - `apps/web/src/services/auth.ts` (token storage) ### ImplĂ©mentation **Étape 1**: Stocker tokens aprĂšs inscription rĂ©ussie **Étape 2**: Rediriger vers dashboard **Étape 3**: Afficher message de succĂšs **Étape 4**: GĂ©rer cas d'erreur ### Code Snippets **apps/web/src/pages/auth/Register.tsx**: ```typescript import { useNavigate } from 'react-router-dom'; import { RegisterForm } from '@/components/forms/RegisterForm'; import { register } from '@/services/api/auth'; import { saveTokens } from '@/services/auth'; import { useToast } from '@/hooks/useToast'; export function RegisterPage() { const navigate = useNavigate(); const { showToast } = useToast(); const handleRegister = async (data: { email: string; password: string; passwordConfirm: string }) => { try { const response = await register({ email: data.email, password: data.password, password_confirm: data.passwordConfirm, }); // Save tokens saveTokens(response.token.access_token, response.token.refresh_token); // Show success message showToast('Registration successful!', 'success'); // Redirect to dashboard navigate('/dashboard'); } catch (error: any) { showToast(error.response?.data?.error || 'Registration failed', 'error'); } }; return (

Create Account

); } ``` ### Definition of Done - [x] Stockage tokens aprĂšs inscription - [x] Redirection vers dashboard - [x] Message de succĂšs affichĂ© - [x] Gestion erreurs - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0161: Create Login Endpoint ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0155 ✅, T0154 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST `/api/v1/auth/login` pour la connexion utilisateur. Valider credentials, gĂ©nĂ©rer JWT et refresh token. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/dto/login_request.go` - `veza-backend-api/internal/handlers/auth_handler.go` (ajouter Login) ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/auth_handler.go` (ajouter mĂ©thode Login) - `veza-backend-api/cmd/api/main.go` (ajouter route) ### ImplĂ©mentation **Étape 1**: CrĂ©er DTO LoginRequest **Étape 2**: CrĂ©er handler Login avec validation credentials **Étape 3**: VĂ©rifier password avec bcrypt **Étape 4**: GĂ©nĂ©rer JWT et refresh token **Étape 5**: Mettre Ă  jour last_login_at ### Code Snippets **veza-backend-api/internal/dto/login_request.go**: ```go package dto type LoginRequest struct { Email string `json:"email" binding:"required,email"` Password string `json:"password" binding:"required"` RememberMe bool `json:"remember_me"` } type LoginResponse struct { User UserResponse `json:"user"` Token TokenResponse `json:"token"` } ``` **veza-backend-api/internal/handlers/auth_handler.go** (ajout): ```go func (h *AuthHandler) Login(c *gin.Context) { var req dto.LoginRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } user, tokens, err := h.authService.Login(req.Email, req.Password, req.RememberMe) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) return } response := dto.LoginResponse{ User: dto.UserResponse{ ID: user.ID, Email: user.Email, }, Token: dto.TokenResponse{ AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, ExpiresIn: 900, // 15 minutes }, } c.JSON(http.StatusOK, response) } ``` ### Definition of Done - [x] Endpoint POST /api/v1/auth/login créé - [x] Validation credentials implĂ©mentĂ©e - [x] JWT et refresh token gĂ©nĂ©rĂ©s - [x] last_login_at mis Ă  jour - [x] Tests unitaires (coverage ≄ 80%) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© --- ## T0162: Implement Credential Validation ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0161 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter validation des credentials avec vĂ©rification email et password hashĂ©. **Note**: Cette fonctionnalitĂ© a Ă©tĂ© implĂ©mentĂ©e dans T0161 via la mĂ©thode `Login` du `AuthService`. ### Fichiers ModifiĂ©s - `veza-backend-api/internal/services/auth_service.go` (mĂ©thode Login existante) ### ImplĂ©mentation **Étape 1**: ✅ Trouver utilisateur par email (implĂ©mentĂ© dans Login ligne 132) **Étape 2**: ✅ VĂ©rifier password avec bcrypt (implĂ©mentĂ© dans Login ligne 140 via passwordService.Compare) **Étape 3**: ✅ Retourner erreur si credentials invalides (implĂ©mentĂ© dans Login lignes 134 et 141) ### Code Snippets **veza-backend-api/internal/services/auth_service.go** (ajout): ```go func (s *AuthService) Login(email, password string, rememberMe bool) (*models.User, *TokenPair, error) { // Find user by email var user models.User if err := s.db.Where("LOWER(email) = LOWER(?)", email).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil, errors.New("invalid credentials") } return nil, nil, err } // Verify password if !s.passwordService.Compare(user.PasswordHash, password) { return nil, nil, errors.New("invalid credentials") } // Update last login user.LastLoginAt = time.Now() s.db.Save(&user) // Generate tokens expiryDays := 30 if rememberMe { expiryDays = 90 } tokens, err := s.jwtService.GenerateTokenPair(user.ID, user.Email) if err != nil { return nil, nil, err } return &user, tokens, nil } ``` ### Definition of Done - [x] Validation credentials implĂ©mentĂ©e (via T0161) - [x] VĂ©rification password avec bcrypt (via T0161) - [x] Gestion erreurs credentials invalides (via T0161) - [x] Tests unitaires (coverage ≄ 80%) (TestAuthService_Login_InvalidEmail, TestAuthService_Login_InvalidPassword) - [x] Code review approuvĂ© --- ## T0163: Implement JWT Token Generation ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0006 ✅, T0161 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter gĂ©nĂ©ration JWT avec payload user_id, email, roles, et expiration 15 minutes. ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/jwt_service.go` (amĂ©liorer GenerateTokenPair) ### ImplĂ©mentation **Étape 1**: CrĂ©er claims JWT avec user_id, email, roles **Étape 2**: GĂ©nĂ©rer access token avec expiration 15min **Étape 3**: GĂ©nĂ©rer refresh token avec expiration 30 jours **Étape 4**: Signer tokens avec secret ### Code Snippets **veza-backend-api/internal/services/jwt_service.go**: ```go package services import ( "time" "github.com/golang-jwt/jwt/v5" ) type JWTService struct { secret []byte accessTTL time.Duration refreshTTL time.Duration } func NewJWTService(secret string) *JWTService { return &JWTService{ secret: []byte(secret), accessTTL: 15 * time.Minute, refreshTTL: 30 * 24 * time.Hour, } } type Claims struct { UserID uint `json:"user_id"` Email string `json:"email"` Roles []string `json:"roles"` jwt.RegisteredClaims } type TokenPair struct { AccessToken string RefreshToken string } func (s *JWTService) GenerateTokenPair(userID uint, email string) (*TokenPair, error) { // Generate access token accessClaims := &Claims{ UserID: userID, Email: email, Roles: []string{"user"}, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.accessTTL)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims) accessTokenString, err := accessToken.SignedString(s.secret) if err != nil { return nil, err } // Generate refresh token refreshClaims := &Claims{ UserID: userID, Email: email, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.refreshTTL)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims) refreshTokenString, err := refreshToken.SignedString(s.secret) if err != nil { return nil, err } return &TokenPair{ AccessToken: accessTokenString, RefreshToken: refreshTokenString, }, nil } ``` ### Definition of Done - [x] JWT Service avec GenerateTokenPair (implĂ©mentĂ©) - [x] Access token avec expiration 15min (dĂ©jĂ  existant, vĂ©rifiĂ©) - [x] Refresh token avec expiration 30 jours (modifiĂ© de 7 Ă  30 jours) - [x] Claims avec user_id, email, role (implĂ©mentĂ© dans Claims struct) - [x] Tests unitaires (coverage ≄ 80%) (TestGenerateTokenPair, TestGenerateTokenPair_WithDifferentUsers, TestGenerateTokenPair_ClaimsIncludeUserIdEmailRole) - [x] Code review approuvĂ© --- ## T0164: Implement Refresh Token Management ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0163 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter gestion refresh tokens avec stockage en base et validation. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/refresh_token_service.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/models/refresh_token.go` (si nĂ©cessaire) ### ImplĂ©mentation **Étape 1**: CrĂ©er RefreshTokenService **Étape 2**: Stocker refresh token en base **Étape 3**: Valider refresh token **Étape 4**: Supprimer refresh token aprĂšs utilisation ### Code Snippets **veza-backend-api/internal/services/refresh_token_service.go**: ```go package services import ( "crypto/sha256" "encoding/hex" "gorm.io/gorm" "veza/internal/models" ) type RefreshTokenService struct { db *gorm.DB } func NewRefreshTokenService(db *gorm.DB) *RefreshTokenService { return &RefreshTokenService{db: db} } func (s *RefreshTokenService) Store(userID uint, token string) error { tokenHash := s.hashToken(token) refreshToken := &models.RefreshToken{ UserID: userID, TokenHash: tokenHash, ExpiresAt: time.Now().Add(30 * 24 * time.Hour), } return s.db.Create(refreshToken).Error } func (s *RefreshTokenService) Validate(userID uint, token string) (bool, error) { tokenHash := s.hashToken(token) var refreshToken models.RefreshToken err := s.db.Where("user_id = ? AND token_hash = ?", userID, tokenHash). First(&refreshToken).Error if err != nil { return false, err } if time.Now().After(refreshToken.ExpiresAt) { return false, nil } return true, nil } func (s *RefreshTokenService) Revoke(userID uint, token string) error { tokenHash := s.hashToken(token) return s.db.Where("user_id = ? AND token_hash = ?", userID, tokenHash). Delete(&models.RefreshToken{}).Error } func (s *RefreshTokenService) hashToken(token string) string { hash := sha256.Sum256([]byte(token)) return hex.EncodeToString(hash[:]) } ``` ### Definition of Done - [x] RefreshTokenService créé (refresh_token_service.go) - [x] Stockage refresh token en base (mĂ©thode Store avec hash SHA-256) - [x] Validation refresh token (mĂ©thode Validate avec vĂ©rification expiration) - [x] Revocation refresh token (mĂ©thodes Revoke et RevokeAll) - [x] Tests unitaires (coverage ≄ 80%) (12 tests couvrant tous les cas d'usage) - [x] Code review approuvĂ© --- ## T0165: Implement Login Service ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0161 ✅, T0162 ✅, T0163 ✅, T0164 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service de connexion qui orchestre validation credentials, gĂ©nĂ©ration tokens, et stockage refresh token. ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/auth_service.go` (complĂ©ter Login) ### ImplĂ©mentation **Étape 1**: Valider credentials avec EmailValidator et PasswordService **Étape 2**: GĂ©nĂ©rer JWT et refresh token **Étape 3**: Stocker refresh token en base **Étape 4**: Mettre Ă  jour last_login_at **Étape 5**: Retourner user et tokens ### Code Snippets **veza-backend-api/internal/services/auth_service.go** (complet): ```go func (s *AuthService) Login(email, password string, rememberMe bool) (*models.User, *TokenPair, error) { // Find user by email var user models.User if err := s.db.Where("LOWER(email) = LOWER(?)", email).First(&user).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, nil, errors.New("invalid credentials") } return nil, nil, err } // Verify password if !s.passwordService.Compare(user.PasswordHash, password) { return nil, nil, errors.New("invalid credentials") } // Update last login user.LastLoginAt = time.Now() s.db.Save(&user) // Generate tokens tokens, err := s.jwtService.GenerateTokenPair(user.ID, user.Email) if err != nil { return nil, nil, err } // Store refresh token if err := s.refreshTokenService.Store(user.ID, tokens.RefreshToken); err != nil { return nil, nil, err } return &user, tokens, nil } ``` ### Definition of Done - [x] Login service complet avec toutes dĂ©pendances (RefreshTokenService intĂ©grĂ©) - [x] Validation credentials (via PasswordService.Compare) - [x] GĂ©nĂ©ration tokens (via JWTService.GenerateTokenPair) - [x] Stockage refresh token (via RefreshTokenService.Store avec expiration 30/90 jours selon rememberMe) - [x] Mise Ă  jour last_login_at (implĂ©mentĂ©) - [x] Tests unitaires (coverage ≄ 80%) (TestAuthService_Login_StoresRefreshToken, TestAuthService_Login_RememberMe_ExtendedExpiry, TestAuthService_Login_RefreshTokenNotStoredIfServiceNil) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© --- ## T0166: Create Login Form Component ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0161 ✅, T0101 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er composant formulaire de connexion avec champs email, password, et checkbox "Remember Me". ### Fichiers Ă  CrĂ©er - `apps/web/src/pages/auth/Login.tsx` - `apps/web/src/components/forms/LoginForm.tsx` ### ImplĂ©mentation **Étape 1**: CrĂ©er composant LoginForm avec champs email, password **Étape 2**: Ajouter checkbox "Remember Me" **Étape 3**: Ajouter validation Zod schema **Étape 4**: Ajouter gestion Ă©tat formulaire ### Code Snippets **apps/web/src/components/forms/LoginForm.tsx**: ```typescript import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Checkbox } from '@/components/ui/Checkbox'; const loginSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(1, 'Password is required'), rememberMe: z.boolean().optional(), }); type LoginFormData = z.infer; export function LoginForm({ onSubmit }: { onSubmit: (data: LoginFormData) => Promise }) { const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(loginSchema), }); const [isLoading, setIsLoading] = useState(false); const handleFormSubmit = async (data: LoginFormData) => { setIsLoading(true); try { await onSubmit(data); } finally { setIsLoading(false); } }; return (
); } ``` ### Definition of Done - [x] LoginForm component créé (apps/web/src/components/forms/LoginForm.tsx) - [x] Validation Zod schema implĂ©mentĂ©e (email, password, rememberMe) - [x] Checkbox "Remember Me" ajoutĂ©e avec Ă©tat gĂ©rĂ© - [x] Page Login créée (apps/web/src/pages/auth/Login.tsx) - [x] Tests unitaires (coverage ≄ 80%) (10 tests couvrant validation, soumission, Ă©tats) - [x] Code review approuvĂ© --- ## T0167: Add Remember Me Functionality ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0166 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter fonctionnalitĂ© "Remember Me" qui Ă©tend la durĂ©e du refresh token Ă  90 jours au lieu de 30. ### Fichiers Ă  Modifier - `apps/web/src/services/api/auth.ts` (passer rememberMe) - `apps/web/src/services/auth.ts` (gĂ©rer expiration) ### ImplĂ©mentation **Étape 1**: Passer rememberMe flag dans login API call **Étape 2**: Stocker rememberMe dans localStorage **Étape 3**: Utiliser rememberMe pour dĂ©terminer expiration token ### Definition of Done - [x] Remember Me flag passĂ© dans API call (fonction login dans auth.ts) - [x] Expiration token gĂ©rĂ©e selon rememberMe (backend gĂšre 30/90 jours, flag stockĂ© dans localStorage) - [x] Page Login.tsx intĂ©grĂ©e avec API et gestion d'erreurs - [x] Tests unitaires (coverage ≄ 80%) (8 tests pour login couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0168: Add Login API Integration ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0166 ✅, T0161 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX (implĂ©mentĂ©e dans T0167) ### Description Technique CrĂ©er service API pour intĂ©grer la connexion avec le backend. ### Fichiers Ă  Modifier - `apps/web/src/services/api/auth.ts` (ajouter login) ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction login dans auth service **Étape 2**: Appeler endpoint POST /api/v1/auth/login **Étape 3**: GĂ©rer tokens dans response **Étape 4**: GĂ©rer erreurs API ### Code Snippets **apps/web/src/services/api/auth.ts** (ajout): ```typescript export interface LoginRequest { email: string; password: string; remember_me?: boolean; } export interface LoginResponse { user: { id: number; email: string; }; token: { access_token: string; refresh_token: string; expires_in: number; }; } export async function login(data: LoginRequest): Promise { const response = await apiClient.post('/auth/login', data); return response.data; } ``` ### Definition of Done - [x] Service login créé (fonction login dans apps/web/src/services/api/auth.ts) - [x] Appel API implĂ©mentĂ© (POST /api/v1/auth/login avec remember_me support) - [x] Gestion tokens (stockage access_token et refresh_token dans localStorage) - [x] Gestion erreurs (comprehensive error handling pour API, rĂ©seau, et erreurs inconnues) - [x] Tests unitaires (coverage ≄ 80%) (8 tests complets pour login créés dans T0167) - [x] Code review approuvĂ© **Note**: Cette tĂąche a Ă©tĂ© complĂ©tĂ©e dans le cadre de T0167 (Add Remember Me Functionality). La fonction `login` est entiĂšrement fonctionnelle avec toutes les fonctionnalitĂ©s requises. --- ## T0169: Add Token Storage Management ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0168 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er gestionnaire de stockage tokens avec localStorage et sĂ©curisation. ### Fichiers Ă  CrĂ©er - `apps/web/src/services/tokenStorage.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er TokenStorage service **Étape 2**: Stocker access token et refresh token **Étape 3**: RĂ©cupĂ©rer tokens **Étape 4**: Supprimer tokens (logout) ### Code Snippets **apps/web/src/services/tokenStorage.ts**: ```typescript const ACCESS_TOKEN_KEY = 'veza_access_token'; const REFRESH_TOKEN_KEY = 'veza_refresh_token'; export class TokenStorage { static setTokens(accessToken: string, refreshToken: string): void { localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); } static getAccessToken(): string | null { return localStorage.getItem(ACCESS_TOKEN_KEY); } static getRefreshToken(): string | null { return localStorage.getItem(REFRESH_TOKEN_KEY); } static clearTokens(): void { localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); } static hasTokens(): boolean { return !!this.getAccessToken() && !!this.getRefreshToken(); } } ``` ### Definition of Done - [x] TokenStorage service créé (apps/web/src/services/tokenStorage.ts) - [x] Stockage tokens dans localStorage (mĂ©thodes setTokens, getAccessToken, getRefreshToken) - [x] RĂ©cupĂ©ration tokens (getAccessToken, getRefreshToken) - [x] Suppression tokens (clearTokens pour logout) - [x] MĂ©thode hasTokens() pour vĂ©rifier la prĂ©sence des tokens - [x] Tests unitaires (coverage ≄ 80%) (15 tests couvrant tous les cas d'usage) - [x] Code review approuvĂ© --- ## T0170: Add Login Error Handling ✅ **Feature Parente**: FEAT-AUTH-002 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0168 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter gestion d'erreurs pour la connexion avec messages d'erreur spĂ©cifiques. ### Fichiers Ă  Modifier - `apps/web/src/pages/auth/Login.tsx` - `apps/web/src/components/forms/LoginForm.tsx` ### ImplĂ©mentation **Étape 1**: GĂ©rer erreur credentials invalides **Étape 2**: Afficher message d'erreur spĂ©cifique **Étape 3**: GĂ©rer erreurs rĂ©seau **Étape 4**: Afficher messages utilisateur-friendly ### Definition of Done - [x] Gestion erreurs credentials invalides (401/403 avec message spĂ©cifique) - [x] Messages d'erreur spĂ©cifiques (fonction getErrorMessage avec mapping des codes) - [x] Gestion erreurs rĂ©seau (NETWORK_ERROR avec message user-friendly) - [x] Gestion erreurs serveur (500, 502, 503) - [x] Gestion rate limiting (429) - [x] Gestion erreurs inconnues - [x] Tests unitaires (coverage ≄ 80%) (10 tests couvrant tous les types d'erreurs) - [x] Code review approuvĂ© --- ## T0171: Implement JWT Service ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0163 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er service JWT complet avec validation, parsing, et extraction claims. ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/jwt_service.go` (ajouter mĂ©thodes) ### ImplĂ©mentation **Étape 1**: Ajouter mĂ©thode ValidateToken **Étape 2**: Ajouter mĂ©thode ParseToken **Étape 3**: Ajouter mĂ©thode ExtractClaims **Étape 4**: Ajouter mĂ©thode GetUserID ### Code Snippets **veza-backend-api/internal/services/jwt_service.go** (ajout): ```go func (s *JWTService) ValidateToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return s.secret, nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") } func (s *JWTService) ExtractUserID(tokenString string) (uint, error) { claims, err := s.ValidateToken(tokenString) if err != nil { return 0, err } return claims.UserID, nil } ``` ### Definition of Done - [x] JWT Service avec validation complĂšte (ValidateToken, VerifyToken alias) - [x] ParseToken implĂ©mentĂ© (alias de ValidateToken) - [x] ExtractClaims implĂ©mentĂ© (alias de ValidateToken) - [x] ExtractUserID implĂ©mentĂ© (extrait UserID depuis token) - [x] Tests unitaires (coverage ≄ 80%) (10 tests couvrant toutes les mĂ©thodes) - [x] Code review approuvĂ© --- ## T0172: Implement Token Refresh Endpoint ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0171 ✅, T0164 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST `/api/v1/auth/refresh` pour rafraĂźchir access token avec refresh token. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/dto/refresh_request.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/auth_handler.go` (ajouter Refresh) ### ImplĂ©mentation **Étape 1**: CrĂ©er DTO RefreshRequest **Étape 2**: CrĂ©er handler Refresh **Étape 3**: Valider refresh token **Étape 4**: GĂ©nĂ©rer nouveau access token ### Code Snippets **veza-backend-api/internal/handlers/auth_handler.go** (ajout): ```go func (h *AuthHandler) Refresh(c *gin.Context) { var req dto.RefreshRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } tokens, err := h.authService.Refresh(req.RefreshToken) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid refresh token"}) return } response := dto.TokenResponse{ AccessToken: tokens.AccessToken, RefreshToken: tokens.RefreshToken, ExpiresIn: 900, } c.JSON(http.StatusOK, response) } ``` ### Definition of Done - [x] Endpoint POST /api/v1/auth/refresh créé (handler Refresh dans AuthHandler) - [x] DTO RefreshRequest créé (apps/web/src/internal/dto/refresh_request.go) - [x] MĂ©thode Refresh dans AuthService (valide refresh token, vĂ©rifie version, gĂ©nĂšre nouveau access token) - [x] Validation refresh token (JWT validation + validation en base via RefreshTokenService) - [x] GĂ©nĂ©ration nouveau access token (via JWTService.GenerateAccessToken) - [x] Route configurĂ©e dans routes.go - [x] Tests unitaires (coverage ≄ 80%) (6 tests pour handler, 6 tests pour service) - [x] Tests intĂ©gration passent - [x] Code review approuvĂ© --- ## T0173: Implement Token Validation Middleware ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: critical **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0171 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er middleware Gin pour valider JWT token dans header Authorization et extraire user context. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/middleware/auth_middleware.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er middleware AuthMiddleware **Étape 2**: Extraire token du header Authorization **Étape 3**: Valider token avec JWT Service **Étape 4**: Ajouter user context dans Gin context ### Code Snippets **veza-backend-api/internal/middleware/auth_middleware.go**: ```go package middleware import ( "strings" "github.com/gin-gonic/gin" "veza/internal/services" ) func AuthMiddleware(jwtService *services.JWTService) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.JSON(401, gin.H{"error": "Authorization header required"}) c.Abort() return } parts := strings.Split(authHeader, " ") if len(parts) != 2 || parts[0] != "Bearer" { c.JSON(401, gin.H{"error": "Invalid authorization header format"}) c.Abort() return } token := parts[1] claims, err := jwtService.ValidateToken(token) if err != nil { c.JSON(401, gin.H{"error": "Invalid token"}) c.Abort() return } c.Set("user_id", claims.UserID) c.Set("user_email", claims.Email) c.Set("user_roles", claims.Roles) c.Next() } } ``` ### Definition of Done - [x] AuthMiddleware créé (veza-backend-api/internal/middleware/auth_middleware.go) - [x] Extraction token du header Authorization (vĂ©rifie format Bearer) - [x] Validation token (utilise JWTService.ValidateToken) - [x] User context ajoutĂ© (user_id, user_email, user_role, token_version) - [x] Tests unitaires (coverage ≄ 80%) (9 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0174: Implement Token Blacklist ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0173 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter blacklist de tokens pour invalider tokens aprĂšs logout ou rĂ©vocation. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/token_blacklist.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er TokenBlacklist service avec Redis **Étape 2**: Ajouter token Ă  blacklist **Étape 3**: VĂ©rifier token dans blacklist **Étape 4**: Expirer tokens aprĂšs TTL ### Definition of Done - [x] TokenBlacklist service créé (veza-backend-api/internal/services/token_blacklist.go) - [x] Ajout token Ă  blacklist (mĂ©thode Add avec TTL) - [x] VĂ©rification blacklist (mĂ©thode IsBlacklisted) - [x] Expiration automatique (TTL Redis pour expiration automatique) - [x] Hash SHA-256 des tokens pour sĂ©curitĂ© - [x] Tests unitaires (coverage ≄ 80%) (12 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0175: Implement Token Expiration Handling ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0173 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter gestion expiration tokens avec refresh automatique et messages d'erreur appropriĂ©s. ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/auth_middleware.go` ### ImplĂ©mentation **Étape 1**: DĂ©tecter token expirĂ© **Étape 2**: Retourner erreur 401 avec message spĂ©cifique **Étape 3**: Ajouter header pour indiquer token expirĂ© ### Definition of Done - [x] DĂ©tection token expirĂ© (dĂ©tection via erreur "expired" dans JWTService.ValidateToken) - [x] Erreur 401 avec message spĂ©cifique ("Token expired. Please refresh your token.") - [x] Header token expired (header X-Token-Expired: true) - [x] Tests unitaires (coverage ≄ 80%) (4 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0176: Implement Token Refresh Logic ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0169 ✅, T0172 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter logique de refresh token cĂŽtĂ© frontend avec appel API et mise Ă  jour tokens. ### Fichiers Ă  CrĂ©er - `apps/web/src/services/tokenRefresh.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction refreshToken **Étape 2**: Appeler endpoint POST /api/v1/auth/refresh **Étape 3**: Mettre Ă  jour tokens stockĂ©s **Étape 4**: GĂ©rer erreurs refresh ### Code Snippets **apps/web/src/services/tokenRefresh.ts**: ```typescript import { apiClient } from './api/client'; import { TokenStorage } from './tokenStorage'; export async function refreshToken(): Promise { const refreshToken = TokenStorage.getRefreshToken(); if (!refreshToken) { throw new Error('No refresh token available'); } try { const response = await apiClient.post<{ access_token: string; refresh_token: string; expires_in: number; }>('/auth/refresh', { refresh_token: refreshToken }); TokenStorage.setTokens(response.data.access_token, response.data.refresh_token); } catch (error) { TokenStorage.clearTokens(); throw error; } } ``` ### Definition of Done - [x] Fonction refreshToken créée (apps/web/src/services/tokenRefresh.ts) - [x] Appel API refresh implĂ©mentĂ© (POST /auth/refresh avec refresh_token) - [x] Mise Ă  jour tokens (TokenStorage.setTokens avec nouveaux tokens) - [x] Gestion erreurs (clearTokens en cas d'Ă©chec, vĂ©rification refresh token disponible) - [x] Tests unitaires (coverage ≄ 80%) (8 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0177: Add Automatic Token Refresh ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0176 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter refresh automatique du token avant expiration avec interceptor axios. ### Fichiers Ă  Modifier - `apps/web/src/services/api/client.ts` (ajouter interceptor) ### ImplĂ©mentation **Étape 1**: CrĂ©er interceptor axios pour dĂ©tecter 401 **Étape 2**: Refresh token automatiquement sur 401 **Étape 3**: Retry request original avec nouveau token **Étape 4**: GĂ©rer cas refresh Ă©chouĂ© ### Definition of Done - [x] Interceptor axios créé (apps/web/src/services/api/client.ts) - [x] DĂ©tection 401 automatique (interceptor response dĂ©tecte status 401) - [x] Refresh automatique (appelle refreshToken() sur 401) - [x] Retry request (retry la requĂȘte originale avec nouveau token) - [x] Queue de requĂȘtes (Ă©vite refresh multiples simultanĂ©s) - [x] Gestion refresh Ă©chouĂ© (rejette les requĂȘtes en queue si refresh Ă©choue) - [x] Tests unitaires (coverage ≄ 80%) (tests de base pour interceptors) - [x] Code review approuvĂ© --- ## T0178: Add Token Expiration Handling ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0176 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter gestion expiration token avec dĂ©tection et redirection vers login si refresh Ă©choue. ### Fichiers Ă  Modifier - `apps/web/src/services/api/client.ts` ### ImplĂ©mentation **Étape 1**: DĂ©tecter token expirĂ© **Étape 2**: Tenter refresh automatique **Étape 3**: Rediriger vers login si refresh Ă©choue **Étape 4**: Afficher message utilisateur ### Definition of Done - [x] DĂ©tection expiration token (via 401 et header X-Token-Expired) - [x] Refresh automatique (dĂ©jĂ  implĂ©mentĂ© dans T0177) - [x] Redirection login si Ă©chec (window.location.href = '/login' quand refresh Ă©choue) - [x] Message utilisateur (message stockĂ© dans sessionStorage et affichĂ© sur page login) - [x] Nettoyage tokens (TokenStorage.clearTokens() avant redirection) - [x] Tests unitaires (coverage ≄ 80%) (tests pour redirection et message) - [x] Code review approuvĂ© --- ## T0179: Add Logout Functionality ✅ **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter fonctionnalitĂ© logout avec suppression tokens et appel API backend. ### Fichiers Ă  CrĂ©er - `apps/web/src/services/api/auth.ts` (ajouter logout) ### Fichiers Ă  Modifier - `apps/web/src/services/auth.ts` (ajouter logout) ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction logout dans API service **Étape 2**: Appeler endpoint POST /api/v1/auth/logout **Étape 3**: Supprimer tokens du storage **Étape 4**: Rediriger vers login ### Code Snippets **apps/web/src/services/api/auth.ts** (ajout): ```typescript export async function logout(): Promise { try { await apiClient.post('/auth/logout'); } finally { TokenStorage.clearTokens(); } } ``` ### Definition of Done - [x] Fonction logout créée (apps/web/src/services/api/auth.ts) - [x] Appel API logout (POST /api/v1/auth/logout) - [x] Suppression tokens (TokenStorage.clearTokens() dans finally block) - [x] Redirection login (gĂ©rĂ©e par Header.tsx via navigate('/login')) - [x] Gestion erreurs (tokens supprimĂ©s mĂȘme si API Ă©choue) - [x] IntĂ©gration store (auth store utilise logout du service API) - [x] Tests unitaires (coverage ≄ 80%) (6 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0180: Add Session Persistence ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-003 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter persistance session avec vĂ©rification token au chargement et restauration Ă©tat utilisateur. ### Fichiers Ă  CrĂ©er - `apps/web/src/hooks/useAuth.ts` ### ImplĂ©mentation **Étape 1**: CrĂ©er hook useAuth **Étape 2**: VĂ©rifier tokens au chargement **Étape 3**: Valider token avec API **Étape 4**: Restaurer Ă©tat utilisateur ### Code Snippets **apps/web/src/hooks/useAuth.ts**: ```typescript import { useEffect, useState } from 'react'; import { TokenStorage } from '@/services/tokenStorage'; import { apiClient } from '@/services/api/client'; export function useAuth() { const [isAuthenticated, setIsAuthenticated] = useState(false); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const checkAuth = async () => { if (!TokenStorage.hasTokens()) { setIsLoading(false); return; } try { // Validate token with backend await apiClient.get('/auth/me'); setIsAuthenticated(true); } catch { TokenStorage.clearTokens(); setIsAuthenticated(false); } finally { setIsLoading(false); } }; checkAuth(); }, []); return { isAuthenticated, isLoading }; } ``` ### Definition of Done - [x] Hook useAuth créé (apps/web/src/hooks/useAuth.ts) - [x] VĂ©rification tokens au chargement (TokenStorage.hasTokens()) - [x] Validation token avec API (apiClient.get('/auth/me')) - [x] Restauration Ă©tat utilisateur (isAuthenticated state) - [x] Nettoyage tokens si invalides (TokenStorage.clearTokens() sur erreur) - [x] Tests unitaires (coverage ≄ 80%) (7 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0181: Create Email Verification Token Model ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er modĂšle EmailVerificationToken dans la base de donnĂ©es avec migration pour stocker tokens de vĂ©rification email. ### Fichiers Ă  CrĂ©er - `veza-backend-api/migrations/018_create_email_verification_tokens.sql` ### ImplĂ©mentation **Étape 1**: CrĂ©er migration pour table email_verification_tokens **Étape 2**: Ajouter colonnes (id, user_id, token, expires_at, used, created_at) **Étape 3**: Ajouter index sur token et user_id **Étape 4**: Ajouter foreign key vers users ### Code Snippets **veza-backend-api/migrations/018_create_email_verification_tokens.sql**: ```sql CREATE TABLE email_verification_tokens ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMP NOT NULL, used BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_email_verification_tokens_token ON email_verification_tokens(token); CREATE INDEX idx_email_verification_tokens_user_id ON email_verification_tokens(user_id); CREATE INDEX idx_email_verification_tokens_expires_at ON email_verification_tokens(expires_at); ``` ### Definition of Done - [x] Migration créée (veza-backend-api/migrations/018_create_email_verification_tokens.sql) - [x] Table email_verification_tokens créée avec toutes colonnes requises - [x] Index sur token, user_id, expires_at créés - [x] Foreign key vers users avec CASCADE DELETE - [x] Migration testĂ©e et appliquĂ©e - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0182: Implement Email Verification Service ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0181 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter service EmailVerificationService avec gĂ©nĂ©ration tokens, validation, et expiration. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/email_verification_service.go` - `veza-backend-api/internal/services/email_verification_service_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er EmailVerificationService struct **Étape 2**: ImplĂ©menter GenerateToken (token alĂ©atoire sĂ©curisĂ©) **Étape 3**: ImplĂ©menter StoreToken (sauvegarde en DB avec expiration 24h) **Étape 4**: ImplĂ©menter VerifyToken (validation token, expiration, marquage utilisĂ©) **Étape 5**: ImplĂ©menter InvalidateOldTokens (invalidation tokens prĂ©cĂ©dents) ### Code Snippets **veza-backend-api/internal/services/email_verification_service.go**: ```go package services import ( "context" "crypto/rand" "encoding/base64" "database/sql" "fmt" "time" "veza-backend-api/internal/database" "go.uber.org/zap" ) type EmailVerificationService struct { db *database.Database logger *zap.Logger } func NewEmailVerificationService(db *database.Database, logger *zap.Logger) *EmailVerificationService { return &EmailVerificationService{ db: db, logger: logger, } } func (s *EmailVerificationService) GenerateToken() (string, error) { bytes := make([]byte, 32) if _, err := rand.Read(bytes); err != nil { return "", fmt.Errorf("failed to generate token: %w", err) } return base64.URLEncoding.EncodeToString(bytes), nil } func (s *EmailVerificationService) StoreToken(userID int64, token string) error { ctx := context.Background() expiresAt := time.Now().Add(24 * time.Hour) _, err := s.db.ExecContext(ctx, "INSERT INTO email_verification_tokens (user_id, token, expires_at, used) VALUES ($1, $2, $3, FALSE)", userID, token, expiresAt, ) return err } func (s *EmailVerificationService) VerifyToken(token string) (int64, error) { ctx := context.Background() var userID int64 var expiresAt time.Time var used bool err := s.db.QueryRowContext(ctx, "SELECT user_id, expires_at, used FROM email_verification_tokens WHERE token = $1", token, ).Scan(&userID, &expiresAt, &used) if err == sql.ErrNoRows { return 0, fmt.Errorf("invalid token") } if err != nil { return 0, fmt.Errorf("failed to verify token: %w", err) } if used { return 0, fmt.Errorf("token already used") } if time.Now().After(expiresAt) { return 0, fmt.Errorf("token expired") } // Mark as used _, err = s.db.ExecContext(ctx, "UPDATE email_verification_tokens SET used = TRUE WHERE token = $1", token) if err != nil { return 0, fmt.Errorf("failed to mark token as used: %w", err) } return userID, nil } func (s *EmailVerificationService) InvalidateOldTokens(userID int64) error { ctx := context.Background() _, err := s.db.ExecContext(ctx, "UPDATE email_verification_tokens SET used = TRUE WHERE user_id = $1 AND used = FALSE", userID, ) return err } ``` ### Definition of Done - [x] EmailVerificationService créé (veza-backend-api/internal/services/email_verification_service.go) - [x] GenerateToken implĂ©mentĂ© (token alĂ©atoire 32 bytes, base64 URL-safe) - [x] StoreToken implĂ©mentĂ© (expiration 24h, insertion DB) - [x] VerifyToken implĂ©mentĂ© (validation, expiration, marquage utilisĂ©) - [x] InvalidateOldTokens implĂ©mentĂ© (invalidation tokens prĂ©cĂ©dents pour user) - [x] Tests unitaires (coverage ≄ 80%) (12 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0183: Create Email Verification Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0182 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint GET /api/v1/auth/verify-email pour vĂ©rifier token et marquer email comme vĂ©rifiĂ©. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/email_verification_handler_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/email_verification_handler.go` (modifier handler existant) - `veza-backend-api/internal/api/routes.go` (mettre Ă  jour route) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler VerifyEmail **Étape 2**: Extraire token depuis query parameter **Étape 3**: Appeler EmailVerificationService.VerifyToken **Étape 4**: Mettre Ă  jour user.is_verified = TRUE **Étape 5**: Retourner rĂ©ponse succĂšs ### Code Snippets **veza-backend-api/internal/handlers/email_verification_handler.go**: ```go package handlers import ( "context" "net/http" "veza-backend-api/internal/database" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "go.uber.org/zap" ) func VerifyEmail(emailVerificationService *services.EmailVerificationService, db *database.Database, logger *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { token := c.Query("token") if token == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "token is required"}) return } userID, err := emailVerificationService.VerifyToken(token) if err != nil { // Gestion erreurs (token invalide, expirĂ©, dĂ©jĂ  utilisĂ©) if err.Error() == "invalid token" { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid token"}) return } if err.Error() == "token expired" { c.JSON(http.StatusBadRequest, gin.H{"error": "token expired"}) return } if err.Error() == "token already used" { c.JSON(http.StatusBadRequest, gin.H{"error": "token already used"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to verify token"}) return } // Mettre Ă  jour user.is_verified = TRUE ctx := context.Background() _, err = db.ExecContext(ctx, ` UPDATE users SET is_verified = TRUE, updated_at = NOW() WHERE id = $1 `, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update user"}) return } c.JSON(http.StatusOK, gin.H{ "message": "Email verified successfully", "user_id": userID, }) } } ``` ### Definition of Done - [x] Handler VerifyEmail créé (veza-backend-api/internal/handlers/email_verification_handler.go) - [x] Route GET /api/v1/auth/verify-email ajoutĂ©e (routes.go) - [x] Extraction token depuis query parameter - [x] Appel EmailVerificationService.VerifyToken - [x] Mise Ă  jour user.is_verified = TRUE - [x] Gestion erreurs (token invalide, expirĂ©, dĂ©jĂ  utilisĂ©) - [x] Tests unitaires (coverage ≄ 80%) (8 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0184: Send Verification Email on Registration ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0182 ✅, T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique IntĂ©grer envoi email de vĂ©rification lors de l'inscription utilisateur avec token et lien de vĂ©rification. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/auth_service_email_verification_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/auth_service.go` (mĂ©thode Register et NewAuthService) - `veza-backend-api/internal/services/email_service.go` (modifier SendVerificationEmail) - `veza-backend-api/internal/routes/routes.go` (mettre Ă  jour NewAuthService) - `veza-backend-api/internal/services/auth_service_test.go` (mettre Ă  jour setupTestAuthService) ### ImplĂ©mentation **Étape 1**: Modifier Register pour gĂ©nĂ©rer token aprĂšs crĂ©ation user **Étape 2**: Modifier mĂ©thode SendVerificationEmail dans EmailService pour accepter email et token **Étape 3**: GĂ©nĂ©rer URL de vĂ©rification avec token **Étape 4**: Construire email HTML avec lien **Étape 5**: Envoyer email via SMTP ### Code Snippets **veza-backend-api/internal/services/auth_service.go** (modification): ```go // T0184: Ajout de EmailVerificationService et EmailService dans AuthService type AuthService struct { // ... autres champs ... emailVerificationService *EmailVerificationService emailService *EmailService logger *zap.Logger } // Dans Register, aprĂšs crĂ©ation de l'utilisateur: // T0184: Étape 1 - GĂ©nĂ©rer token de vĂ©rification aprĂšs crĂ©ation user if s.emailVerificationService != nil && s.emailService != nil { // Generate verification token token, err := s.emailVerificationService.GenerateToken() if err != nil { // Log l'erreur mais ne pas faire Ă©chouer l'inscription s.logger.Warn("Failed to generate verification token", zap.Error(err)) } else { // Store token if err := s.emailVerificationService.StoreToken(user.ID, token); err != nil { s.logger.Warn("Failed to store verification token", zap.Error(err)) } else { // Send verification email if err := s.emailService.SendVerificationEmail(user.Email, token); err != nil { s.logger.Warn("Failed to send verification email", zap.Error(err)) // Don't fail registration if email fails } } } } ``` **veza-backend-api/internal/services/email_service.go** (modification): ```go // T0184: Accepte email et token (le token est gĂ©nĂ©rĂ© et stockĂ© par EmailVerificationService) func (es *EmailService) SendVerificationEmail(email, token string) error { // T0184: Étape 3 - GĂ©nĂ©rer URL de vĂ©rification avec token baseURL := os.Getenv("FRONTEND_URL") if baseURL == "" { baseURL = "http://localhost:5173" } verifyURL := fmt.Sprintf("%s/verify-email?token=%s", baseURL, token) // T0184: Étape 4 - Construire email HTML avec lien subject := "Verify your Veza account" body := es.buildVerificationEmailHTML(verifyURL) // T0184: Étape 5 - Envoyer email via SMTP (gestion erreurs sans faire Ă©chouer registration) return es.sendEmail(email, subject, body) } ``` ### Definition of Done - [x] Register modifiĂ© pour gĂ©nĂ©rer token aprĂšs crĂ©ation user - [x] SendVerificationEmail modifiĂ© dans EmailService pour accepter email et token - [x] URL de vĂ©rification gĂ©nĂ©rĂ©e (FRONTEND_URL + /verify-email?token=...) - [x] Email HTML construit avec lien de vĂ©rification - [x] Email envoyĂ© via SMTP (gestion erreurs sans faire Ă©chouer registration) - [x] Token stockĂ© en DB avec expiration 24h (via EmailVerificationService.StoreToken) - [x] Tests unitaires (coverage ≄ 80%) (10 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0185: Create Email Verification Frontend Page ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0183 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er page frontend /verify-email pour afficher statut vĂ©rification et permettre renvoi email. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/pages/VerifyEmailPage.tsx` - `apps/web/src/features/auth/services/emailVerificationService.ts` - `apps/web/src/features/auth/pages/VerifyEmailPage.test.tsx` - `apps/web/src/features/auth/services/emailVerificationService.test.ts` ### Fichiers Ă  Modifier - `apps/web/src/router/index.tsx` (ajouter route /verify-email) - `apps/web/src/components/ui/LazyComponent.tsx` (ajouter LazyVerifyEmail) ### ImplĂ©mentation **Étape 1**: CrĂ©er VerifyEmailPage component **Étape 2**: Extraire token depuis URL query parameter **Étape 3**: Appeler API GET /api/v1/auth/verify-email?token=... **Étape 4**: Afficher statut (vĂ©rification en cours, succĂšs, erreur) **Étape 5**: Ajouter bouton "Retry" en cas d'erreur ### Code Snippets **apps/web/src/features/auth/pages/VerifyEmailPage.tsx**: ```typescript import { useState, useEffect } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { verifyEmail, type ApiError } from '../services/emailVerificationService'; import { Button } from '@/components/ui/button'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; export function VerifyEmailPage() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying'); const [message, setMessage] = useState('Verifying your email...'); const token = searchParams.get('token'); useEffect(() => { if (!token) { setStatus('error'); setMessage('Invalid verification link'); return; } verifyEmailHandler(); }, [token]); const verifyEmailHandler = async () => { // Appel API et gestion des erreurs // Affichage du statut avec LoadingSpinner, message de succĂšs ou erreur // Redirection vers /login aprĂšs 3 secondes en cas de succĂšs }; return ( {/* Affichage selon le statut */} ); } ``` ### Definition of Done - [x] VerifyEmailPage créé (apps/web/src/features/auth/pages/VerifyEmailPage.tsx) - [x] Route /verify-email ajoutĂ©e (router/index.tsx) - [x] Extraction token depuis URL query parameter - [x] Appel API GET /api/v1/auth/verify-email?token=... (via emailVerificationService) - [x] Affichage statut (verifying, success, error) - [x] Redirection vers /login aprĂšs succĂšs (3 secondes) - [x] Bouton retry en cas d'erreur - [x] Tests unitaires (coverage ≄ 80%) (6+ tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0186: Add Resend Verification Email Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0182 ✅, T0184 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST /api/v1/auth/resend-verification pour renvoyer email de vĂ©rification. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/resend_verification_handler_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/email_verification_handler.go` (modifier ResendVerificationEmail) - `veza-backend-api/internal/api/routes.go` (mettre Ă  jour route) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler ResendVerificationEmail **Étape 2**: Valider email dans request body **Étape 3**: VĂ©rifier que email n'est pas dĂ©jĂ  vĂ©rifiĂ© **Étape 4**: Invalider anciens tokens **Étape 5**: GĂ©nĂ©rer nouveau token et envoyer email ### Code Snippets **veza-backend-api/internal/handlers/email_verification_handler.go** (modification): ```go type ResendVerificationRequest struct { Email string `json:"email" binding:"required,email"` } func ResendVerificationEmail( emailVerificationService *services.EmailVerificationService, emailService *services.EmailService, db *database.Database, logger *zap.Logger, ) gin.HandlerFunc { return func(c *gin.Context) { // Valider email dans request body var req ResendVerificationRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // VĂ©rifier que l'utilisateur existe ctx := context.Background() var userID int64 var isVerified bool err := db.QueryRowContext(ctx, ` SELECT id, is_verified FROM users WHERE email = $1 `, req.Email).Scan(&userID, &isVerified) if err == sql.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } // VĂ©rifier que email n'est pas dĂ©jĂ  vĂ©rifiĂ© if isVerified { c.JSON(http.StatusBadRequest, gin.H{"error": "email already verified"}) return } // Invalider anciens tokens emailVerificationService.InvalidateOldTokens(userID) // GĂ©nĂ©rer nouveau token token, err := emailVerificationService.GenerateToken() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"}) return } // Stocker le token if err := emailVerificationService.StoreToken(userID, token); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to store token"}) return } // Envoyer email if err := emailService.SendVerificationEmail(req.Email, token); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to send email"}) return } c.JSON(http.StatusOK, gin.H{"message": "verification email sent"}) } } ``` ### Definition of Done - [x] Handler ResendVerificationEmail créé - [x] Route POST /api/v1/auth/resend-verification ajoutĂ©e - [x] Validation email et user existe - [x] VĂ©rification email pas dĂ©jĂ  vĂ©rifiĂ© - [x] Invalidation anciens tokens - [x] GĂ©nĂ©ration et envoi nouveau token - [x] Tests unitaires (coverage ≄ 80%) (8 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0187: Add Resend Verification Email Frontend ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0186 ✅, T0185 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter fonctionnalitĂ© renvoi email de vĂ©rification dans VerifyEmailPage et LoginPage. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/components/LoginForm.test.tsx` ### Fichiers Ă  Modifier - `apps/web/src/features/auth/pages/VerifyEmailPage.tsx` - `apps/web/src/features/auth/components/LoginForm.tsx` (ajouter bouton si email non vĂ©rifiĂ©) - `apps/web/src/features/auth/components/RegisterForm.tsx` (stocker email dans localStorage) - `apps/web/src/features/auth/services/emailVerificationService.ts` (ajouter resendVerificationEmail) - `apps/web/src/features/auth/pages/VerifyEmailPage.test.tsx` (ajouter tests pour resend) ### ImplĂ©mentation **Étape 1**: Ajouter fonction resendVerificationEmail dans emailVerificationService **Étape 2**: Ajouter bouton "Resend Email" dans VerifyEmailPage **Étape 3**: GĂ©rer rate limiting (max 1 email par 60 secondes) **Étape 4**: Afficher message de confirmation **Étape 5**: Ajouter bouton dans LoginForm si erreur "email not verified" ### Code Snippets **apps/web/src/features/auth/services/emailVerificationService.ts** (ajout): ```typescript export async function resendVerificationEmail(email: string): Promise { // Appelle POST /api/v1/auth/resend-verification avec gestion d'erreurs } ``` **apps/web/src/features/auth/pages/VerifyEmailPage.tsx** (modification): ```typescript const [resendCooldown, setResendCooldown] = useState(0); const handleResendVerificationEmail = async () => { // RĂ©cupĂšre email depuis localStorage // Appelle resendVerificationEmail // DĂ©finit cooldown de 60 secondes // Affiche message de confirmation }; // Dans le JSX {status === 'error' && ( )} ``` ### Definition of Done - [x] Fonction resendVerificationEmail ajoutĂ©e (emailVerificationService) - [x] Bouton "Resend Email" ajoutĂ© (VerifyEmailPage) - [x] Rate limiting implĂ©mentĂ© (60 secondes cooldown) - [x] Message de confirmation affichĂ© - [x] Bouton ajoutĂ© dans LoginForm si erreur "email not verified" - [x] Email stockĂ© dans localStorage lors de l'inscription - [x] Tests unitaires (coverage ≄ 80%) (6+ tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0188: Add Email Verification Check on Login ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0183 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique VĂ©rifier statut vĂ©rification email lors du login et bloquer si non vĂ©rifiĂ©. ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/auth_service.go` (mĂ©thode Login) - `veza-backend-api/internal/handlers/auth_handler.go` (gestion erreur) - `veza-backend-api/internal/services/auth_service_test.go` (ajouter tests) - `veza-backend-api/internal/handlers/auth_handler_test.go` (ajouter test handler) ### ImplĂ©mentation **Étape 1**: Modifier Login pour vĂ©rifier IsVerified **Étape 2**: Retourner erreur spĂ©cifique si email non vĂ©rifiĂ© **Étape 3**: GĂ©rer erreur cĂŽtĂ© handler avec code 403 **Étape 4**: Frontend gĂšre erreur et affiche message (dĂ©jĂ  implĂ©mentĂ© dans T0187) ### Code Snippets **veza-backend-api/internal/services/auth_service.go** (modification): ```go // T0188: VĂ©rifier que l'email est vĂ©rifiĂ© if !user.IsVerified { return nil, nil, fmt.Errorf("email not verified: please check your inbox for verification link") } ``` **veza-backend-api/internal/handlers/auth_handler.go** (modification): ```go // T0188: GĂ©rer l'erreur si l'email n'est pas vĂ©rifiĂ© avec code 403 if strings.Contains(err.Error(), "email not verified") { c.JSON(http.StatusForbidden, gin.H{ "error": err.Error(), "code": "EMAIL_NOT_VERIFIED", }) return } ``` ### Definition of Done - [x] VĂ©rification IsVerified ajoutĂ©e dans Login - [x] Erreur spĂ©cifique retournĂ©e si email non vĂ©rifiĂ© - [x] Code erreur 403 avec code "EMAIL_NOT_VERIFIED" - [x] Frontend gĂšre erreur et affiche message (T0187) - [x] Bouton resend visible dans message d'erreur (T0187) - [x] Tests unitaires (coverage ≄ 80%) (7+ tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0189: Clean Expired Verification Tokens ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0181 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er job de nettoyage pour supprimer tokens de vĂ©rification expirĂ©s et utilisĂ©s. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/jobs/cleanup_verification_tokens.go` - `veza-backend-api/internal/jobs/cleanup_verification_tokens_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er fonction CleanupExpiredVerificationTokens **Étape 2**: Supprimer tokens expirĂ©s (expires_at < NOW()) **Étape 3**: Supprimer tokens utilisĂ©s plus anciens que 7 jours **Étape 4**: Programmer job quotidien avec ScheduleCleanupJob ### Code Snippets **veza-backend-api/internal/jobs/cleanup_verification_tokens.go**: ```go func CleanupExpiredVerificationTokens(db *database.Database, logger *zap.Logger) error { ctx := context.Background() now := time.Now() sevenDaysAgo := now.Add(-7 * 24 * time.Hour) result, err := db.ExecContext(ctx, ` DELETE FROM email_verification_tokens WHERE expires_at < $1 OR (used = TRUE AND created_at < $2) `, now, sevenDaysAgo) // Logging du nombre de tokens supprimĂ©s rowsAffected, _ := result.RowsAffected() logger.Info("Cleaned up verification tokens", zap.Int64("count", rowsAffected)) return nil } func ScheduleCleanupJob(db *database.Database, logger *zap.Logger) { ticker := time.NewTicker(24 * time.Hour) go func() { // ExĂ©cuter immĂ©diatement au dĂ©marrage CleanupExpiredVerificationTokens(db, logger) // Puis exĂ©cuter toutes les 24 heures for range ticker.C { CleanupExpiredVerificationTokens(db, logger) } }() } ``` ### Definition of Done - [x] Fonction CleanupExpiredVerificationTokens créée - [x] Suppression tokens expirĂ©s (expires_at < NOW()) - [x] Suppression tokens utilisĂ©s > 7 jours - [x] Job programmĂ© pour exĂ©cution quotidienne - [x] Logging du nombre de tokens supprimĂ©s - [x] Tests unitaires (coverage ≄ 80%) (5 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0190: Add Email Verification Status to User Profile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-004 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0183 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter champ is_verified dans rĂ©ponse API /users/me et afficher badge dans profil utilisateur. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/components/EmailVerificationBadge.tsx` - `apps/web/src/features/auth/components/EmailVerificationBadge.test.tsx` ### Fichiers Ă  Modifier - `apps/web/src/features/user/components/ProfileForm.tsx` (afficher badge Ă  cĂŽtĂ© de l'email) - `apps/web/src/components/layout/Header.tsx` (afficher badge dans menu utilisateur si non vĂ©rifiĂ©) ### ImplĂ©mentation **Étape 1**: VĂ©rifier que IsVerified est dĂ©jĂ  dans UserResponse (dĂ©jĂ  prĂ©sent) **Étape 2**: VĂ©rifier que le service retourne is_verified (dĂ©jĂ  prĂ©sent) **Étape 3**: CrĂ©er composant EmailVerificationBadge **Étape 4**: Afficher badge dans ProfileForm Ă  cĂŽtĂ© du champ email **Étape 5**: Afficher badge dans Header si email non vĂ©rifiĂ© ### Code Snippets **apps/web/src/features/auth/components/EmailVerificationBadge.tsx**: ```typescript interface EmailVerificationBadgeProps { verified: boolean; } export function EmailVerificationBadge({ verified }: EmailVerificationBadgeProps) { if (verified) { return ( ✓ Email Verified ); } return ( ⚠ Email Not Verified ); } ``` ### Definition of Done - [x] Champ IsVerified dĂ©jĂ  prĂ©sent dans UserResponse (backend) - [x] Champ is_verified dĂ©jĂ  prĂ©sent dans User type (frontend) - [x] Service retourne is_verified depuis la base de donnĂ©es - [x] Composant EmailVerificationBadge créé - [x] Badge affichĂ© dans ProfileForm Ă  cĂŽtĂ© du champ email - [x] Badge visible dans header menu utilisateur si non vĂ©rifiĂ© - [x] Tests unitaires (coverage ≄ 80%) (4 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0191: Create Password Reset Token Model ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er modĂšle PasswordResetToken dans la base de donnĂ©es avec migration pour stocker tokens de rĂ©initialisation mot de passe. ### Fichiers Ă  CrĂ©er - `veza-backend-api/migrations/019_create_password_reset_tokens.sql` - `veza-backend-api/internal/database/migrations_password_reset_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/database/database.go` (ajouter migration Ă  la liste) ### ImplĂ©mentation **Étape 1**: CrĂ©er migration pour table password_reset_tokens **Étape 2**: Ajouter colonnes (id, user_id, token, expires_at, used, created_at) **Étape 3**: Ajouter index sur token, user_id et expires_at **Étape 4**: Ajouter foreign key vers users avec CASCADE DELETE ### Code Snippets **veza-backend-api/migrations/019_create_password_reset_tokens.sql**: ```sql CREATE TABLE password_reset_tokens ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMP NOT NULL, used BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_password_reset_tokens_token ON password_reset_tokens(token); CREATE INDEX idx_password_reset_tokens_user_id ON password_reset_tokens(user_id); CREATE INDEX idx_password_reset_tokens_expires_at ON password_reset_tokens(expires_at); ``` ### Definition of Done - [x] Migration créée (veza-backend-api/migrations/019_create_password_reset_tokens.sql) - [x] Table password_reset_tokens créée avec toutes colonnes requises - [x] Index sur token, user_id, expires_at créés - [x] Foreign key vers users avec CASCADE DELETE - [x] Migration ajoutĂ©e Ă  la liste dans database.go - [x] Tests unitaires (coverage ≄ 80%) (4 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0192: Implement Password Reset Service ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0191 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter service PasswordResetService avec gĂ©nĂ©ration tokens, validation, et expiration. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/password_reset_service.go` - `veza-backend-api/internal/services/password_reset_service_test.go` ### ImplĂ©mentation **Étape 1**: CrĂ©er PasswordResetService struct **Étape 2**: ImplĂ©menter GenerateToken (token alĂ©atoire sĂ©curisĂ©) **Étape 3**: ImplĂ©menter StoreToken (sauvegarde en DB avec expiration 1h) **Étape 4**: ImplĂ©menter VerifyToken (validation token, expiration, vĂ©rification utilisĂ©) **Étape 5**: ImplĂ©menter MarkTokenAsUsed (marquage token utilisĂ©) **Étape 6**: ImplĂ©menter InvalidateOldTokens (invalidation tokens prĂ©cĂ©dents) ### Code Snippets **veza-backend-api/internal/services/password_reset_service.go**: ```go type PasswordResetService struct { db *database.Database logger *zap.Logger } func (s *PasswordResetService) GenerateToken() (string, error) { // GĂ©nĂšre token alĂ©atoire 32 bytes, base64 URL-safe } func (s *PasswordResetService) StoreToken(userID int64, token string) error { // Stocke token avec expiration 1h } func (s *PasswordResetService) VerifyToken(token string) (int64, error) { // Valide token, vĂ©rifie expiration et s'il n'est pas dĂ©jĂ  utilisĂ© } func (s *PasswordResetService) MarkTokenAsUsed(token string) error { // Marque token comme utilisĂ© } func (s *PasswordResetService) InvalidateOldTokens(userID int64) error { // Invalide tous les tokens prĂ©cĂ©dents pour un utilisateur } ``` ### Definition of Done - [x] PasswordResetService créé (veza-backend-api/internal/services/password_reset_service.go) - [x] GenerateToken implĂ©mentĂ© (token alĂ©atoire 32 bytes, base64 URL-safe) - [x] StoreToken implĂ©mentĂ© (expiration 1h, insertion DB) - [x] VerifyToken implĂ©mentĂ© (validation, expiration, vĂ©rification utilisĂ©) - [x] MarkTokenAsUsed implĂ©mentĂ© (marquage token utilisĂ©) - [x] InvalidateOldTokens implĂ©mentĂ© (invalidation tokens prĂ©cĂ©dents pour user) - [x] Tests unitaires (coverage ≄ 80%) (12 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0193: Create Request Password Reset Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0192 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST /api/v1/auth/password/reset-request pour demander rĂ©initialisation mot de passe. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/password_reset_handler.go` - `veza-backend-api/internal/handlers/password_reset_handler_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/api/routes.go` (ajouter route et initialiser services) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler RequestPasswordReset **Étape 2**: Extraire email depuis request body **Étape 3**: VĂ©rifier que user existe via PasswordService.GetUserByEmail **Étape 4**: Invalider anciens tokens **Étape 5**: GĂ©nĂ©rer token et le stocker **Étape 6**: Envoyer email avec lien de rĂ©initialisation **Étape 7**: Retourner rĂ©ponse succĂšs (toujours pour sĂ©curitĂ©) ### Code Snippets **veza-backend-api/internal/handlers/password_reset_handler.go**: ```go func RequestPasswordReset( passwordResetService *services.PasswordResetService, passwordService *services.PasswordService, emailService *services.EmailService, logger *zap.Logger, ) gin.HandlerFunc { // Handler qui gĂ©nĂšre et envoie token de rĂ©initialisation } ``` ### Definition of Done - [x] Handler RequestPasswordReset créé - [x] Route POST /api/v1/auth/password/reset-request ajoutĂ©e - [x] Validation email dans request body - [x] Recherche user par email (via PasswordService.GetUserByEmail) - [x] GĂ©nĂ©ration et stockage token (via PasswordResetService) - [x] Invalidation anciens tokens avant gĂ©nĂ©ration - [x] Envoi email avec lien de rĂ©initialisation (via EmailService.SendPasswordResetEmail) - [x] RĂ©ponse gĂ©nĂ©rique (prĂ©vention email enumeration) - [x] Gestion d'erreurs avec logging appropriĂ© - [x] Tests unitaires (coverage ≄ 80%) (8 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0194: Create Reset Password Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0192 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint POST /api/v1/auth/password/reset pour rĂ©initialiser mot de passe avec token. ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/password_reset_handler.go` (ajouter ResetPassword) - `veza-backend-api/internal/services/password_service.go` (ajouter UpdatePassword) - `veza-backend-api/internal/services/session_service.go` (ajouter RevokeAllUserSessionsByUserID) - `veza-backend-api/internal/api/routes.go` (ajouter route) - `veza-backend-api/internal/handlers/password_reset_handler_test.go` (ajouter tests) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler ResetPassword **Étape 2**: Extraire token et nouveau mot de passe depuis request body **Étape 3**: Valider token avec PasswordResetService.VerifyToken **Étape 4**: Valider nouveau mot de passe (force, longueur) via PasswordService.ValidatePassword **Étape 5**: Hasher nouveau mot de passe et mettre Ă  jour user via PasswordService.UpdatePassword **Étape 6**: Marquer token comme utilisĂ© via PasswordResetService.MarkTokenAsUsed **Étape 7**: Invalider toutes les sessions utilisateur via SessionService.RevokeAllUserSessionsByUserID ### Code Snippets **veza-backend-api/internal/handlers/password_reset_handler.go** (ajout): ```go type ResetPasswordRequest struct { Token string `json:"token" binding:"required"` NewPassword string `json:"new_password" binding:"required,min=8"` } func ResetPassword( passwordResetService *services.PasswordResetService, passwordService *services.PasswordService, sessionService *services.SessionService, logger *zap.Logger, ) gin.HandlerFunc { // Handler qui rĂ©initialise le mot de passe avec token } ``` ### Definition of Done - [x] Handler ResetPassword créé - [x] Route POST /api/v1/auth/password/reset ajoutĂ©e - [x] Extraction token et nouveau mot de passe - [x] Validation token avec VerifyToken - [x] Validation force du mot de passe (via PasswordService.ValidatePassword) - [x] Mise Ă  jour mot de passe user (hash bcrypt via PasswordService.UpdatePassword) - [x] Marquage token comme utilisĂ© (via PasswordResetService.MarkTokenAsUsed) - [x] Invalidation sessions utilisateur (via SessionService.RevokeAllUserSessionsByUserID) - [x] Gestion d'erreurs avec logging appropriĂ© - [x] Tests unitaires (coverage ≄ 80%) (10 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0195: Send Password Reset Email ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0193 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er mĂ©thode SendPasswordResetEmail dans EmailService pour envoyer email avec lien de rĂ©initialisation. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/email_service_password_reset_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/email_service.go` (mĂ©thode SendPasswordResetEmail existe dĂ©jĂ ) ### ImplĂ©mentation **Étape 1**: MĂ©thode SendPasswordResetEmail existe dĂ©jĂ  ✓ **Étape 2**: GĂ©nĂšre URL de rĂ©initialisation avec token (FRONTEND_URL + /reset-password?token=...) ✓ **Étape 3**: Construit email HTML avec lien via buildPasswordResetEmail ✓ **Étape 4**: Envoie email via SMTP via sendEmail ✓ ### Code Snippets **veza-backend-api/internal/services/email_service.go**: ```go func (es *EmailService) SendPasswordResetEmail(userID int64, email string, token string) error { // Build reset URL baseURL := os.Getenv("FRONTEND_URL") if baseURL == "" { baseURL = "http://localhost:5173" } resetURL := fmt.Sprintf("%s/reset-password?token=%s", baseURL, token) // Prepare email content subject := "Reset your Veza password" body := es.buildPasswordResetEmail(resetURL) // Send email via SMTP return es.sendEmail(email, subject, body) } ``` ### Definition of Done - [x] MĂ©thode SendPasswordResetEmail créée (existe dĂ©jĂ ) - [x] URL de rĂ©initialisation gĂ©nĂ©rĂ©e (FRONTEND_URL + /reset-password?token=...) - [x] Email HTML construit avec lien de rĂ©initialisation (via buildPasswordResetEmail) - [x] Message d'expiration (1 heure) inclus dans le template HTML - [x] Message de sĂ©curitĂ© inclus ("If you didn't request this, please ignore this email") - [x] Email envoyĂ© via SMTP (via sendEmail) - [x] Tests unitaires (coverage ≄ 80%) (7 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0196: Create Password Reset Frontend Pages ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0193 ✅, T0194 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er pages frontend pour demander et rĂ©initialiser mot de passe (ForgotPasswordPage et ResetPasswordPage). ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/pages/ForgotPasswordPage.test.tsx` - `apps/web/src/features/auth/pages/ResetPasswordPage.test.tsx` ### Fichiers Ă  Modifier - `apps/web/src/features/auth/pages/ForgotPasswordPage.tsx` (dĂ©jĂ  existe, vĂ©rifiĂ©) - `apps/web/src/features/auth/components/ForgotPasswordForm.tsx` (implĂ©menter appel API) - `apps/web/src/features/auth/pages/ResetPasswordPage.tsx` (corriger appel API et export default) - `apps/web/src/router/index.tsx` (ajouter route /reset-password) - `apps/web/src/components/ui/LazyComponent.tsx` (ajouter LazyResetPassword) ### ImplĂ©mentation **Étape 1**: ForgotPasswordPage existe dĂ©jĂ  ✓ **Étape 2**: ForgotPasswordForm implĂ©mente appel API avec apiClient ✓ **Étape 3**: ResetPasswordPage existe dĂ©jĂ  ✓ **Étape 4**: ResetPasswordPage extrait token depuis URL avec useSearchParams ✓ **Étape 5**: ResetPasswordPage utilise apiClient pour appeler /auth/password/reset ✓ **Étape 6**: Validation formulaires avec react-hook-form et zod ✓ **Étape 7**: Messages de succĂšs/erreur affichĂ©s ✓ **Étape 8**: Routes ajoutĂ©es dans router ✓ ### Code Snippets **apps/web/src/features/auth/components/ForgotPasswordForm.tsx**: ```typescript const onSubmit = async (data: ForgotPasswordFormData) => { await apiClient.post('/auth/password/reset-request', { email: data.email, }); setIsSubmitted(true); }; ``` **apps/web/src/features/auth/pages/ResetPasswordPage.tsx**: ```typescript const [searchParams] = useSearchParams(); const token = searchParams.get('token'); await apiClient.post('/auth/password/reset', { token, new_password: newPassword, }); ``` ### Definition of Done - [x] ForgotPasswordPage créé (existe dĂ©jĂ ) - [x] ResetPasswordPage créé (existe dĂ©jĂ ) - [x] Routes /forgot-password et /reset-password ajoutĂ©es - [x] Extraction token depuis URL dans ResetPasswordPage (useSearchParams) - [x] Appels API implĂ©mentĂ©s (apiClient.post) - [x] Validation formulaires (react-hook-form, zod) - [x] Messages de succĂšs/erreur affichĂ©s (Alert, toast) - [x] Tests unitaires (coverage ≄ 80%) (8 tests pour ForgotPasswordPage, 10 tests pour ResetPasswordPage) - [x] Code review approuvĂ© --- ## T0197: Add Password Strength Validation ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0194 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter validation force du mot de passe avec rĂšgles (longueur, majuscules, chiffres, caractĂšres spĂ©ciaux). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/utils/password_validator.go` - `veza-backend-api/internal/utils/password_validator_test.go` - `apps/web/src/lib/passwordValidator.ts` - `apps/web/src/lib/passwordValidator.test.ts` ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/password_service.go` (utilise ValidatePasswordStrength) - `veza-backend-api/internal/utils/utils.go` (supprimĂ© ancienne fonction) - `apps/web/src/components/forms/PasswordStrengthIndicator.tsx` (utilise validatePasswordStrength) - `apps/web/src/schemas/validation.ts` (min 8 chars) ### ImplĂ©mentation **Étape 1**: PasswordValidator backend créé dans utils/password_validator.go ✓ **Étape 2**: RĂšgles implĂ©mentĂ©es (min 8 chars, majuscule, minuscule, chiffre, spĂ©cial) ✓ **Étape 3**: PasswordService.ValidatePassword utilise utils.ValidatePasswordStrength ✓ **Étape 4**: PasswordValidator frontend créé dans lib/passwordValidator.ts ✓ **Étape 5**: PasswordStrengthIndicator utilise validatePasswordStrength ✓ **Étape 6**: passwordSchema mis Ă  jour avec min 8 chars ✓ ### Code Snippets **veza-backend-api/internal/utils/password_validator.go**: ```go func ValidatePasswordStrength(password string) error { if len(password) < 8 { return fmt.Errorf("password must be at least 8 characters") } var hasUpper, hasLower, hasNumber, hasSpecial bool for _, char := range password { switch { case unicode.IsUpper(char): hasUpper = true case unicode.IsLower(char): hasLower = true case unicode.IsNumber(char): hasNumber = true case unicode.IsPunct(char) || unicode.IsSymbol(char): hasSpecial = true } } if !hasUpper { return fmt.Errorf("password must contain at least one uppercase letter") } if !hasLower { return fmt.Errorf("password must contain at least one lowercase letter") } if !hasNumber { return fmt.Errorf("password must contain at least one number") } if !hasSpecial { return fmt.Errorf("password must contain at least one special character") } return nil } ``` ### Definition of Done - [x] PasswordValidator backend créé (utils/password_validator.go) - [x] RĂšgles de validation implĂ©mentĂ©es (min 8 chars, majuscule, minuscule, chiffre, spĂ©cial) - [x] PasswordValidator frontend créé (lib/passwordValidator.ts) - [x] Indicateur force mot de passe affichĂ© (PasswordStrengthIndicator mis Ă  jour) - [x] Validation frontend avant envoi (passwordSchema mis Ă  jour) - [x] Messages d'erreur descriptifs - [x] Tests unitaires (coverage ≄ 80%) (10 tests backend, 16 tests frontend) - [x] Code review approuvĂ© --- ## T0198: Add Link to Forgot Password in Login ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 30min **DĂ©pendances**: T0196 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter lien "Forgot Password?" dans LoginPage pointant vers ForgotPasswordPage. ### Fichiers Ă  Modifier - `apps/web/src/features/auth/pages/LoginPage.test.tsx` (ajout tests) - `apps/web/src/features/auth/components/LoginForm.tsx` (lien dĂ©jĂ  prĂ©sent) ### ImplĂ©mentation **Étape 1**: Lien "Forgot Password?" prĂ©sent dans LoginForm (utilisĂ© par LoginPage) ✓ **Étape 2**: Lien pointe vers /forgot-password ✓ **Étape 3**: Styling cohĂ©rent avec design (text-primary hover:underline) ✓ **Étape 4**: Tests unitaires ajoutĂ©s pour vĂ©rifier prĂ©sence et route ✓ ### Code Snippets **apps/web/src/features/auth/components/LoginForm.tsx** (lien existant): ```typescript {t('auth.login.forgotPassword')} ``` **apps/web/src/features/auth/pages/LoginPage.test.tsx** (tests ajoutĂ©s): ```typescript it('displays "Forgot Password?" link', () => { const forgotPasswordLink = screen.getByRole('link', { name: /auth.login.forgotPassword/i, }); expect(forgotPasswordLink).toBeInTheDocument(); expect(forgotPasswordLink).toHaveAttribute('href', '/forgot-password'); }); ``` ### Definition of Done - [x] Lien "Forgot Password?" prĂ©sent dans LoginForm (utilisĂ© par LoginPage) - [x] Lien pointe vers /forgot-password - [x] Styling cohĂ©rent avec design (text-primary hover:underline) - [x] Tests unitaires (coverage ≄ 80%) (2 tests ajoutĂ©s) - [x] Code review approuvĂ© --- ## T0199: Clean Expired Password Reset Tokens ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0191 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er job de nettoyage pour supprimer tokens de rĂ©initialisation expirĂ©s et utilisĂ©s. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/jobs/cleanup_password_reset_tokens.go` - `veza-backend-api/internal/jobs/cleanup_password_reset_tokens_test.go` ### ImplĂ©mentation **Étape 1**: Fonction CleanupExpiredPasswordResetTokens créée ✓ **Étape 2**: Suppression tokens expirĂ©s (expires_at < NOW()) ✓ **Étape 3**: Suppression tokens utilisĂ©s plus anciens que 7 jours ✓ **Étape 4**: Fonction SchedulePasswordResetCleanupJob créée pour exĂ©cution quotidienne ✓ ### Code Snippets **veza-backend-api/internal/jobs/cleanup_password_reset_tokens.go**: ```go func CleanupExpiredPasswordResetTokens(db *database.Database, logger *zap.Logger) error { ctx := context.Background() now := time.Now() sevenDaysAgo := now.Add(-7 * 24 * time.Hour) result, err := db.ExecContext(ctx, ` DELETE FROM password_reset_tokens WHERE expires_at < $1 OR (used = TRUE AND created_at < $2) `, now, sevenDaysAgo) if err != nil { logger.Error("Failed to cleanup expired password reset tokens", zap.Error(err)) return err } rowsAffected, err := result.RowsAffected() if err != nil { logger.Warn("Failed to get rows affected count", zap.Error(err)) } else { logger.Info("Cleaned up password reset tokens", zap.Int64("count", rowsAffected)) } return nil } ``` ### Definition of Done - [x] Fonction CleanupExpiredPasswordResetTokens créée - [x] Suppression tokens expirĂ©s (expires_at < NOW()) - [x] Suppression tokens utilisĂ©s > 7 jours - [x] Fonction SchedulePasswordResetCleanupJob créée pour exĂ©cution quotidienne - [x] Logging du nombre de tokens supprimĂ©s - [x] Tests unitaires (coverage ≄ 80%) (4 tests couvrant tous les cas) - [x] Code review approuvĂ© --- ## T0200: Invalidate Sessions on Password Reset ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-005 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0194 ✅, T0174 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter invalidation de toutes les sessions utilisateur lors de la rĂ©initialisation du mot de passe. ### Fichiers Ă  Modifier - `veza-backend-api/internal/services/auth_service.go` (ajouter InvalidateAllUserSessions) - `veza-backend-api/internal/handlers/password_reset_handler.go` (appeler invalidation) - `veza-backend-api/internal/middleware/auth_middleware.go` (vĂ©rifier token_version) - `veza-backend-api/internal/api/routes.go` (passer db au middleware) - `veza-backend-api/internal/services/auth_service_test.go` (ajouter 6 tests) ### ImplĂ©mentation **Étape 1**: MĂ©thode InvalidateAllUserSessions créée dans AuthService ✓ **Étape 2**: Mise Ă  jour token_version dans user ✓ **Étape 3**: Invalidation appelĂ©e dans ResetPassword handler ✓ **Étape 4**: Middleware vĂ©rifie token_version lors validation ✓ ### Code Snippets **veza-backend-api/internal/services/auth_service.go** (ajout): ```go func (s *AuthService) InvalidateAllUserSessions(userID int64, sessionService interface { RevokeAllUserSessionsByUserID(ctx context.Context, userID int64) (int64, error) }) error { // T0200: Mettre Ă  jour token_version pour invalider tous les tokens existants result := s.db.Model(&models.User{}). Where("id = ?", userID). Update("token_version", gorm.Expr("token_version + 1")) // RĂ©voquer toutes les sessions actives de l'utilisateur if sessionService != nil { ctx := context.Background() sessionService.RevokeAllUserSessionsByUserID(ctx, userID) } return nil } ``` **veza-backend-api/internal/middleware/auth_middleware.go** (ajout): ```go // T0200: VĂ©rifier token_version contre la DB pour invalider les tokens aprĂšs reset password if db != nil { var user models.User if err := db.Where("id = ?", claims.UserID).First(&user).Error; err == nil { if claims.TokenVersion != user.TokenVersion { c.JSON(http.StatusUnauthorized, gin.H{"error": "Token has been invalidated. Please login again."}) c.Abort() return } } } ``` ### Definition of Done - [x] MĂ©thode InvalidateAllUserSessions créée - [x] Mise Ă  jour token_version dans user - [x] Invalidation appelĂ©e dans ResetPassword handler - [x] Middleware vĂ©rifie token_version lors validation - [x] Tokens existants rejetĂ©s aprĂšs reset (via token_version check) - [x] Tests unitaires (coverage ≄ 80%) (6 tests ajoutĂ©s) - [x] Code review approuvĂ© --- ## T0201: Create Session Model ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er modĂšle Session dans la base de donnĂ©es pour tracker sessions actives utilisateurs. ### Fichiers Ă  CrĂ©er - `veza-backend-api/migrations/020_create_sessions.sql` - `veza-backend-api/internal/database/migrations_sessions_test.go` ### ImplĂ©mentation **Étape 1**: Migration créée pour table sessions ✓ **Étape 2**: Colonnes ajoutĂ©es (id, user_id, token_hash, ip_address, user_agent, expires_at, created_at, last_activity) ✓ **Étape 3**: Index sur user_id, token_hash et expires_at créés ✓ **Étape 4**: Foreign key vers users avec CASCADE DELETE ajoutĂ©e ✓ ### Code Snippets **veza-backend-api/migrations/020_create_sessions.sql**: ```sql -- T0201: Create sessions table for tracking active user sessions CREATE TABLE sessions ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(255) NOT NULL UNIQUE, ip_address VARCHAR(45), user_agent TEXT, expires_at TIMESTAMP NOT NULL, last_activity TIMESTAMP NOT NULL DEFAULT NOW(), created_at TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_sessions_user_id ON sessions(user_id); CREATE INDEX idx_sessions_token_hash ON sessions(token_hash); CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); ``` ### Definition of Done - [x] Migration créée (020_create_sessions.sql) - [x] Table sessions créée avec toutes colonnes requises - [x] Index sur user_id, token_hash, expires_at créés - [x] Foreign key vers users avec CASCADE DELETE - [x] Migration ajoutĂ©e Ă  la liste dans database.go - [x] Tests unitaires (coverage ≄ 80%) (6 tests créés) - [x] Code review approuvĂ© --- ## T0202: Implement Session Service ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0201 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique ImplĂ©menter SessionService pour crĂ©er, valider, mettre Ă  jour et supprimer sessions. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/services/session_service.go` (mĂ©thodes T0202 ajoutĂ©es) - `veza-backend-api/internal/services/session_service_t0202_test.go` (tests unitaires) ### ImplĂ©mentation **Étape 1**: SessionService struct existe dĂ©jĂ  ✓ **Étape 2**: CreateSessionWithBIGINT implĂ©mentĂ© (crĂ©ation session avec token hash) ✓ **Étape 3**: GetSessionWithBIGINT implĂ©mentĂ© (rĂ©cupĂ©ration session par token hash) ✓ **Étape 4**: UpdateLastActivity implĂ©mentĂ© (mise Ă  jour last_activity) ✓ **Étape 5**: DeleteSession implĂ©mentĂ© (suppression session) ✓ **Étape 6**: DeleteAllUserSessions implĂ©mentĂ© (suppression toutes sessions user) ✓ ### Note Les mĂ©thodes T0202 utilisent BIGINT user_id pour correspondre Ă  la migration T0201. Elles sont prĂ©fixĂ©es avec "WithBIGINT" pour Ă©viter les conflits avec les mĂ©thodes existantes qui utilisent UUID. ### Code Snippets **veza-backend-api/internal/services/session_service.go**: ```go package services import ( "crypto/sha256" "database/sql" "encoding/hex" "time" "go.uber.org/zap" ) type SessionService struct { db *sql.DB logger *zap.Logger } func NewSessionService(db *sql.DB, logger *zap.Logger) *SessionService { return &SessionService{ db: db, logger: logger, } } func (s *SessionService) CreateSession(userID int64, token string, ipAddress, userAgent string, expiresAt time.Time) error { tokenHash := hashToken(token) _, err := s.db.Exec( `INSERT INTO sessions (user_id, token_hash, ip_address, user_agent, expires_at, last_activity) VALUES ($1, $2, $3, $4, $5, NOW())`, userID, tokenHash, ipAddress, userAgent, expiresAt, ) return err } func (s *SessionService) GetSession(tokenHash string) (*Session, error) { var session Session err := s.db.QueryRow( `SELECT id, user_id, token_hash, ip_address, user_agent, expires_at, last_activity, created_at FROM sessions WHERE token_hash = $1`, tokenHash, ).Scan(&session.ID, &session.UserID, &session.TokenHash, &session.IPAddress, &session.UserAgent, &session.ExpiresAt, &session.LastActivity, &session.CreatedAt) if err == sql.ErrNoRows { return nil, fmt.Errorf("session not found") } if err != nil { return nil, err } if time.Now().After(session.ExpiresAt) { return nil, fmt.Errorf("session expired") } return &session, nil } func hashToken(token string) string { hash := sha256.Sum256([]byte(token)) return hex.EncodeToString(hash[:]) } ``` ### Definition of Done - [x] SessionService créé (existe dĂ©jĂ ) - [x] CreateSessionWithBIGINT implĂ©mentĂ© - [x] GetSessionWithBIGINT implĂ©mentĂ© - [x] UpdateLastActivity implĂ©mentĂ© - [x] DeleteSession implĂ©mentĂ© - [x] DeleteAllUserSessions implĂ©mentĂ© - [x] Tests unitaires (coverage ≄ 80%) (15 tests créés) - [x] Code review approuvĂ© --- ## T0203: Track Session on Login ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0202 ✅, T0169 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique IntĂ©grer crĂ©ation session lors du login avec stockage IP et user agent. ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/auth.go` (modifier Login handler) - `veza-backend-api/internal/api/routes.go` (passer sessionService au handler) - `veza-backend-api/internal/handlers/auth_login_t0203_test.go` (tests unitaires) ### ImplĂ©mentation **Étape 1**: IP address et User-Agent extraits depuis request ✓ **Étape 2**: Login modifiĂ© pour crĂ©er session aprĂšs gĂ©nĂ©ration token ✓ **Étape 3**: Token hash stockĂ© dans sessions table ✓ **Étape 4**: Expiration session dĂ©finie Ă  30 jours ✓ ### Code Snippets **veza-backend-api/internal/handlers/auth_handler.go** (modification): ```go func Login(authService *services.AuthService, sessionService *services.SessionService) gin.HandlerFunc { return func(c *gin.Context) { // ... binding request ... resp, err := authService.Login(c.Request.Context(), &req) if err != nil { // ... error handling ... } // Create session ipAddress := c.ClientIP() userAgent := c.GetHeader("User-Agent") expiresAt := time.Now().Add(30 * 24 * time.Hour) // 30 days if err := sessionService.CreateSession( resp.User.ID, resp.AccessToken, ipAddress, userAgent, expiresAt, ); err != nil { // Log but don't fail login } c.JSON(http.StatusOK, resp) } } ``` ### Definition of Done - [x] Extraction IP address et User-Agent - [x] CrĂ©ation session aprĂšs login - [x] Stockage token hash dans sessions - [x] Expiration session dĂ©finie (30 jours) - [x] Gestion erreurs (ne pas faire Ă©chouer login) - [x] Tests unitaires (coverage ≄ 80%) (6 tests créés) - [x] Code review approuvĂ© --- ## T0204: Update Session Activity on Request ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0202 ✅, T0173 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Mettre Ă  jour last_activity de la session lors de chaque requĂȘte authentifiĂ©e. ### Fichiers Ă  Modifier - `veza-backend-api/internal/middleware/auth_middleware.go` (modifiĂ©) - `veza-backend-api/internal/services/session_service.go` (ajout UpdateLastActivityIfNeeded) - `veza-backend-api/internal/api/routes.go` (passer sessionService au middleware) - `veza-backend-api/internal/middleware/auth_middleware_t0204_test.go` (tests middleware) - `veza-backend-api/internal/services/session_service_t0204_test.go` (tests service) ### ImplĂ©mentation **Étape 1**: Token hash extrait dans middleware ✓ **Étape 2**: UpdateLastActivityIfNeeded appelĂ© avec debounce ✓ **Étape 3**: Debounce 5 minutes implĂ©mentĂ© avec cache en mĂ©moire ✓ **Étape 4**: Erreurs gĂ©rĂ©es silencieusement ✓ ### Code Snippets **veza-backend-api/internal/middleware/auth_middleware.go** (modification): ```go func AuthMiddleware(jwtService *services.JWTService, sessionService *services.SessionService) gin.HandlerFunc { return func(c *gin.Context) { // ... validation token existante ... // Update session activity (debounced) tokenHash := hashToken(token) sessionService.UpdateLastActivityIfNeeded(tokenHash, 5*time.Minute) // ... reste du middleware ... } } ``` ### Definition of Done - [x] Extraction token hash dans middleware - [x] UpdateLastActivityIfNeeded appelĂ© avec debounce - [x] Debounce 5 minutes implĂ©mentĂ© (cache en mĂ©moire avec mutex) - [x] Gestion erreurs silencieuse (ne fait pas Ă©chouer la requĂȘte) - [x] Tests unitaires (coverage ≄ 80%) (5 tests middleware + 5 tests service) - [x] Code review approuvĂ© --- ## T0205: Create Get Active Sessions Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: medium **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0202 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint GET /api/v1/auth/sessions pour rĂ©cupĂ©rer liste sessions actives utilisateur. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/session_handler.go` - `veza-backend-api/internal/handlers/session_handler_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/api/routes.go` (ajouter route) ### ImplĂ©mentation **Étape 1**: CrĂ©er handler GetActiveSessions **Étape 2**: RĂ©cupĂ©rer user_id depuis context (middleware) **Étape 3**: Appeler SessionService.GetUserSessions **Étape 4**: Retourner liste sessions avec metadata ### Code Snippets **veza-backend-api/internal/handlers/session_handler.go**: ```go package handlers import ( "net/http" "veza-backend-api/internal/services" "github.com/gin-gonic/gin" "github.com/google/uuid" ) func GetActiveSessions(sessionService *services.SessionService) gin.HandlerFunc { return func(c *gin.Context) { userIDInterface, exists := c.Get("user_id") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) return } userID, ok := userIDInterface.(uuid.UUID) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid user ID type"}) return } sessions, err := sessionService.GetUserSessions(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get sessions"}) return } // Formater les sessions avec metadata et is_current var sessionList []map[string]interface{} for _, session := range sessions { sessionData := map[string]interface{}{ "id": session.ID, "created_at": session.CreatedAt, "expires_at": session.ExpiresAt, "ip_address": session.IPAddress, "user_agent": session.UserAgent, "metadata": session.Metadata, } // Marquer la session actuelle currentSessionID, exists := c.Get("session_id") if exists && currentSessionID.(uuid.UUID) == session.ID { sessionData["is_current"] = true } else { sessionData["is_current"] = false } sessionList = append(sessionList, sessionData) } c.JSON(http.StatusOK, gin.H{ "sessions": sessionList, "count": len(sessionList), }) } } ``` ### Definition of Done - [x] Handler GetActiveSessions créé - [x] Route GET /api/v1/auth/sessions ajoutĂ©e - [x] RĂ©cupĂ©ration user_id depuis context - [x] Liste sessions retournĂ©e avec metadata - [x] Filtrage sessions expirĂ©es (dĂ©jĂ  fait dans SessionService.GetUserSessions) - [x] Tests unitaires (coverage ≄ 80%) (6 tests) - [x] Code review approuvĂ© --- ## T0206: Create Revoke Session Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0202 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint DELETE /api/v1/auth/sessions/:sessionId pour rĂ©voquer une session spĂ©cifique. ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/session_handler.go` (ajouter RevokeSession) - `veza-backend-api/internal/services/session_service.go` (ajouter GetSessionByID) - `veza-backend-api/internal/services/token_blacklist.go` (ajouter AddTokenHash) - `veza-backend-api/internal/api/routes.go` (ajouter route DELETE) - `veza-backend-api/internal/handlers/session_handler_t0206_test.go` (tests unitaires) ### ImplĂ©mentation **Étape 1**: Handler RevokeSession créé ✓ **Étape 2**: session_id extrait depuis URL parameter ✓ **Étape 3**: VĂ©rification ownership session ✓ **Étape 4**: Suppression session et ajout token Ă  blacklist ✓ ### Code Snippets **veza-backend-api/internal/handlers/session_handler.go** (ajout): ```go func RevokeSession(sessionService *services.SessionService, tokenBlacklist *services.TokenBlacklist) gin.HandlerFunc { return func(c *gin.Context) { userID, _ := c.Get("user_id").(int64) sessionID := c.Param("sessionId") // Get session to verify ownership session, err := sessionService.GetSessionByID(sessionID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "session not found"}) return } if session.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "unauthorized"}) return } // Delete session if err := sessionService.DeleteSession(sessionID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to revoke session"}) return } // Add token to blacklist tokenBlacklist.Add(session.TokenHash, session.ExpiresAt) c.JSON(http.StatusOK, gin.H{"message": "session revoked"}) } } ``` ### Definition of Done - [x] Handler RevokeSession créé - [x] Route DELETE /api/v1/auth/sessions/:sessionId ajoutĂ©e - [x] VĂ©rification ownership session - [x] Suppression session - [x] Ajout token Ă  blacklist (avec AddTokenHash) - [x] Tests unitaires (coverage ≄ 80%) (8 tests) - [x] Code review approuvĂ© --- ## T0207: Create Revoke All Sessions Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0202 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er endpoint DELETE /api/v1/auth/sessions pour rĂ©voquer toutes les sessions utilisateur sauf la session actuelle. ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/session_handler.go` (ajouter RevokeAllSessions) - `veza-backend-api/internal/services/session_service.go` (ajouter GetUserSessionsWithBIGINT) - `veza-backend-api/internal/api/routes.go` (ajouter route DELETE /api/v1/auth/sessions) - `veza-backend-api/internal/handlers/session_handler_t0207_test.go` (tests unitaires) ### ImplĂ©mentation **Étape 1**: Handler RevokeAllSessions créé ✓ **Étape 2**: user_id et token actuel extraits depuis context ✓ **Étape 3**: Toutes sessions user rĂ©cupĂ©rĂ©es avec GetUserSessionsWithBIGINT ✓ **Étape 4**: Toutes sessions supprimĂ©es sauf session actuelle ✓ **Étape 5**: Tokens ajoutĂ©s Ă  blacklist ✓ ### Code Snippets **veza-backend-api/internal/handlers/session_handler.go** (ajout): ```go func RevokeAllSessions(sessionService *services.SessionService, tokenBlacklist *services.TokenBlacklist, jwtService *services.JWTService) gin.HandlerFunc { return func(c *gin.Context) { userID, _ := c.Get("user_id").(int64) currentToken := extractToken(c) currentTokenHash := hashToken(currentToken) // Get all user sessions sessions, err := sessionService.GetUserSessions(userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get sessions"}) return } // Revoke all except current for _, session := range sessions { if session.TokenHash != currentTokenHash { sessionService.DeleteSession(session.ID) tokenBlacklist.Add(session.TokenHash, session.ExpiresAt) } } c.JSON(http.StatusOK, gin.H{"message": "all other sessions revoked"}) } } ``` ### Definition of Done - [x] Handler RevokeAllSessions créé - [x] Route DELETE /api/v1/auth/sessions ajoutĂ©e - [x] RĂ©cupĂ©ration toutes sessions user (GetUserSessionsWithBIGINT) - [x] Exclusion session actuelle (comparaison token hash) - [x] Suppression autres sessions - [x] Ajout tokens Ă  blacklist (avec AddTokenHash) - [x] Tests unitaires (coverage ≄ 80%) (6 tests) - [x] Code review approuvĂ© --- ## T0208: Clean Expired Sessions ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0201 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er job de nettoyage pour supprimer sessions expirĂ©es automatiquement. ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/jobs/cleanup_sessions.go` - `veza-backend-api/internal/jobs/cleanup_sessions_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/main.go` (appeler ScheduleCleanupJob au dĂ©marrage) ### ImplĂ©mentation **Étape 1**: Fonction CleanupExpiredSessions créée ✓ **Étape 2**: Utilise SessionService.CleanupExpiredSessions pour supprimer sessions avec expires_at < NOW() ✓ **Étape 3**: Job programmĂ© pour exĂ©cution quotidienne (24h) ✓ ### Code Snippets **veza-backend-api/internal/jobs/cleanup_sessions.go**: ```go package jobs func CleanupExpiredSessions(db *sql.DB, logger *zap.Logger) error { ctx := context.Background() result, err := db.ExecContext(ctx, ` DELETE FROM sessions WHERE expires_at < NOW() `) if err != nil { return err } rowsAffected, _ := result.RowsAffected() logger.Info("Cleaned up expired sessions", zap.Int64("count", rowsAffected)) return nil } ``` ### Definition of Done - [x] Fonction CleanupExpiredSessions créée - [x] Suppression sessions expirĂ©es (utilise SessionService.CleanupExpiredSessions) - [x] Job programmĂ© pour exĂ©cution quotidienne (ScheduleCleanupJob avec ticker 24h) - [x] Logging du nombre de sessions supprimĂ©es - [x] Tests unitaires (coverage ≄ 80%) (4 tests) - [x] Code review approuvĂ© --- ## T0209: Create Sessions Management Frontend Page ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: medium **Complexity**: medium **Temps EstimĂ©**: 2h 30min **DĂ©pendances**: T0205 ✅, T0206 ✅, T0207 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique CrĂ©er page frontend /settings/sessions pour afficher et gĂ©rer sessions actives. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/pages/SessionsPage.tsx` - `apps/web/src/features/auth/pages/SessionsPage.test.tsx` ### Fichiers Ă  Modifier - `apps/web/src/router/index.tsx` (ajouter route /settings/sessions) - `apps/web/src/components/ui/LazyComponent.tsx` (ajouter LazySessions) ### ImplĂ©mentation **Étape 1**: SessionsPage component créé ✓ **Étape 2**: API GET /api/v1/auth/sessions appelĂ©e ✓ **Étape 3**: Liste sessions affichĂ©e avec metadata (IP, user agent, last activity, created_at) ✓ **Étape 4**: Session actuelle marquĂ©e avec badge "Current Session" ✓ **Étape 5**: Boutons "Revoke" ajoutĂ©s pour chaque session (sauf session actuelle) ✓ **Étape 6**: Bouton "Revoke All Other Sessions" ajoutĂ© avec confirmation ✓ ### Code Snippets **apps/web/src/features/auth/pages/SessionsPage.tsx**: ```typescript import { useState, useEffect } from 'react'; import { apiClient } from '@/services/api/client'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; interface Session { id: string; ip_address: string; user_agent: string; last_activity: string; created_at: string; is_current: boolean; } export function SessionsPage() { const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchSessions(); }, []); const fetchSessions = async () => { try { const response = await apiClient.get('/auth/sessions'); setSessions(response.data.sessions); } catch (error) { console.error('Failed to fetch sessions', error); } finally { setLoading(false); } }; const revokeSession = async (sessionId: string) => { try { await apiClient.delete(`/auth/sessions/${sessionId}`); fetchSessions(); } catch (error) { console.error('Failed to revoke session', error); } }; const revokeAllOther = async () => { try { await apiClient.delete('/auth/sessions'); fetchSessions(); } catch (error) { console.error('Failed to revoke sessions', error); } }; return (

Active Sessions

{sessions.map(session => (
{session.ip_address}
{session.user_agent}
Last activity: {session.last_activity}
{session.is_current && Current Session} {!session.is_current && ( )}
))}
); } ``` ### Definition of Done - [x] SessionsPage créé - [x] Route /settings/sessions ajoutĂ©e - [x] Liste sessions affichĂ©e avec metadata (IP, user agent, dates) - [x] Session actuelle marquĂ©e (badge "Current Session") - [x] Bouton "Revoke" pour chaque session (sauf session actuelle) - [x] Bouton "Revoke All Other Sessions" (avec confirmation) - [x] Tests unitaires (coverage ≄ 80%) (9 tests) - [x] Code review approuvĂ© --- ## T0210: Add Session Info to User Profile ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-AUTH-006 **Phase**: 2 **Priority**: low **Complexity**: simple **Temps EstimĂ©**: 1h **DĂ©pendances**: T0205 ✅ **Statut**: ✅ **COMPLÉTÉE** - Date: 2025-01-XX ### Description Technique Ajouter lien vers page sessions dans UserProfile et afficher nombre de sessions actives. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/auth/components/UserProfile.tsx` - `apps/web/src/features/auth/components/UserProfile.test.tsx` ### Fichiers Ă  Modifier - `apps/web/src/features/profile/pages/ProfilePage.tsx` (intĂ©grer UserProfile) ### ImplĂ©mentation **Étape 1**: API GET /api/v1/auth/sessions appelĂ©e dans UserProfile ✓ **Étape 2**: Nombre de sessions actives affichĂ© dans UserProfile ✓ **Étape 3**: Lien vers /settings/sessions ajoutĂ© avec bouton "Manage Sessions" ✓ **Étape 4**: UserProfile intĂ©grĂ© dans ProfilePage avec Card "Security" ✓ ### Code Snippets **apps/web/src/features/auth/components/UserProfile.tsx** (modification): ```typescript const [activeSessionsCount, setActiveSessionsCount] = useState(0); useEffect(() => { apiClient.get('/auth/sessions').then(response => { setActiveSessionsCount(response.data.sessions.length); }); }, []); // Dans le JSX

Active Sessions: {activeSessionsCount}

Manage Sessions
``` ### Definition of Done - [x] RĂ©cupĂ©ration nombre sessions actives (via API GET /auth/sessions) - [x] Affichage nombre sessions dans UserProfile (composant rĂ©utilisable) - [x] Lien vers /settings/sessions ajoutĂ© (bouton "Manage Sessions") - [x] UserProfile intĂ©grĂ© dans ProfilePage (Card "Security") - [x] Tests unitaires (coverage ≄ 80%) (5 tests) - [x] Code review approuvĂ© --- ## T0211: Create Get User Profile Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-PROFILE-001 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0210 ✅ **Statut**: ✅ **TERMINÉ** ### Description Technique CrĂ©er endpoint GET /api/v1/users/{id}/profile pour rĂ©cupĂ©rer profil utilisateur public (username, avatar, bio, location, etc.). ### Fichiers Ă  CrĂ©er - `veza-backend-api/internal/handlers/profile_handler.go` - `veza-backend-api/internal/handlers/profile_handler_test.go` ### Fichiers Ă  Modifier - `veza-backend-api/internal/api/routes.go` (ajouter route GET /api/v1/users/:id/profile) ### ImplĂ©mentation **Étape 1**: CrĂ©er ProfileHandler struct avec mĂ©thode GetProfile **Étape 2**: RĂ©cupĂ©rer user par ID depuis DB **Étape 3**: VĂ©rifier si profil est public (si user diffĂ©rent de requester) **Étape 4**: Retourner profil avec champs publics (username, avatar_url, bio, location, created_at) ### Code Snippets **veza-backend-api/internal/handlers/profile_handler.go**: ```go package handlers import ( "net/http" "strconv" "github.com/gin-gonic/gin" "veza-backend-api/internal/services" ) type ProfileHandler struct { userService *services.UserService } func NewProfileHandler(userService *services.UserService) *ProfileHandler { return &ProfileHandler{userService: userService} } func (h *ProfileHandler) GetProfile(c *gin.Context) { userIDStr := c.Param("id") userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) return } profile, err := h.userService.GetProfile(userID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } c.JSON(http.StatusOK, gin.H{"profile": profile}) } ``` ### Definition of Done - [x] ProfileHandler créé (veza-backend-api/internal/handlers/profile_handler.go) - [x] Route GET /api/v1/users/:id/profile ajoutĂ©e - [x] RĂ©cupĂ©ration user par ID avec validation - [x] VĂ©rification profil public (si user diffĂ©rent de requester) - [x] Retour profil avec champs publics (username, avatar_url, bio, created_at) - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0212: Create Update User Profile Endpoint ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-PROFILE-001 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0211 ✅ **Statut**: ✅ **TERMINÉ** ### Description Technique CrĂ©er endpoint PUT /api/v1/users/{id}/profile pour mettre Ă  jour profil utilisateur (first_name, last_name, username, bio, location, birthdate, gender). ### Fichiers Ă  Modifier - `veza-backend-api/internal/handlers/profile_handler.go` (ajouter mĂ©thode UpdateProfile) - `veza-backend-api/internal/api/routes.go` (ajouter route PUT /api/v1/users/:id/profile) ### ImplĂ©mentation **Étape 1**: CrĂ©er struct UpdateProfileRequest **Étape 2**: Valider user_id (doit correspondre Ă  user authentifiĂ©) **Étape 3**: Valider username (unique, 3-30 chars, alphanumeric + underscore) **Étape 4**: Valider bio (max 500 chars) **Étape 5**: Valider birthdate (format YYYY-MM-DD, > 13 ans) **Étape 6**: Mettre Ă  jour profil en DB **Étape 7**: VĂ©rifier username modifiable (1 fois par mois via username_changed_at) ### Code Snippets **veza-backend-api/internal/handlers/profile_handler.go** (ajout): ```go type UpdateProfileRequest struct { FirstName string `json:"first_name" binding:"omitempty,max=100"` LastName string `json:"last_name" binding:"omitempty,max=100"` Username string `json:"username" binding:"omitempty,min=3,max=30,alphanum"` Bio string `json:"bio" binding:"omitempty,max=500"` Location string `json:"location" binding:"omitempty,max=100"` Birthdate string `json:"birthdate" binding:"omitempty,datetime=2006-01-02"` Gender string `json:"gender" binding:"omitempty,oneof=Male Female Other 'Prefer not to say'"` } func (h *ProfileHandler) UpdateProfile(c *gin.Context) { userIDStr := c.Param("id") userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) return } // VĂ©rifier que user_id correspond Ă  user authentifiĂ© authenticatedUserID := c.GetInt64("user_id") if userID != authenticatedUserID { c.JSON(http.StatusForbidden, gin.H{"error": "cannot update other user's profile"}) return } var req UpdateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Valider username uniqueness si modifiĂ© if req.Username != "" { if err := h.userService.ValidateUsername(userID, req.Username); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } } profile, err := h.userService.UpdateProfile(userID, req) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update profile"}) return } c.JSON(http.StatusOK, gin.H{"profile": profile}) } ``` ### Definition of Done - [x] UpdateProfile handler créé - [x] Route PUT /api/v1/users/:id/profile ajoutĂ©e - [x] Validation user_id (doit correspondre Ă  user authentifiĂ©) - [x] Validation username (unique, 3-30 chars, alphanumeric + underscore) - [x] Validation bio (max 500 chars) - [x] Validation birthdate (format YYYY-MM-DD, > 13 ans) - [x] VĂ©rification username modifiable (1 fois par mois) - [x] Mise Ă  jour profil en DB - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0213: Create Get User Profile Frontend Page ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-PROFILE-001 **Phase**: 2 **Priority**: high **Complexity**: simple **Temps EstimĂ©**: 1h 30min **DĂ©pendances**: T0211 ✅ **Statut**: ✅ **TERMINÉ** ### Description Technique CrĂ©er page frontend pour afficher profil utilisateur public avec tous les dĂ©tails (avatar, username, bio, location, etc.). ### Fichiers Ă  CrĂ©er - `apps/web/src/features/profile/pages/UserProfilePage.tsx` - `apps/web/src/features/profile/pages/UserProfilePage.test.tsx` - `apps/web/src/features/profile/services/profileService.ts` ### Fichiers Ă  Modifier - `apps/web/src/App.tsx` (ajouter route /u/:username) ### ImplĂ©mentation **Étape 1**: CrĂ©er profileService avec getProfile(username) **Étape 2**: CrĂ©er UserProfilePage avec rĂ©cupĂ©ration profil par username **Étape 3**: Afficher avatar, username, bio, location, date de crĂ©ation **Étape 4**: GĂ©rer Ă©tats loading et error **Étape 5**: Ajouter route /u/:username ### Code Snippets **apps/web/src/features/profile/services/profileService.ts**: ```typescript import { apiClient } from '@/services/api/client'; export interface UserProfile { id: number; username: string; first_name: string; last_name: string; avatar_url: string | null; bio: string | null; location: string | null; birthdate: string | null; gender: string | null; created_at: string; } export async function getProfile(userId: number): Promise { const response = await apiClient.get(`/users/${userId}/profile`); return response.data.profile; } export async function getProfileByUsername(username: string): Promise { // Note: backend devra implĂ©menter GET /api/v1/users/by-username/:username const response = await apiClient.get(`/users/by-username/${username}`); return response.data.profile; } ``` **apps/web/src/features/profile/pages/UserProfilePage.tsx**: ```typescript import { useEffect, useState } from 'react'; import { useParams } from 'react-router-dom'; import { getProfileByUsername, UserProfile } from '../services/profileService'; import { Card } from '@/components/ui/Card'; import { Avatar } from '@/components/ui/Avatar'; export function UserProfilePage() { const { username } = useParams<{ username: string }>(); const [profile, setProfile] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!username) return; getProfileByUsername(username) .then(setProfile) .catch(err => setError(err.message)) .finally(() => setLoading(false)); }, [username]); if (loading) return
Loading...
; if (error) return
Error: {error}
; if (!profile) return
User not found
; return (

{profile.username}

{profile.first_name && profile.last_name && (

{profile.first_name} {profile.last_name}

)} {profile.bio &&

{profile.bio}

} {profile.location &&

📍 {profile.location}

}

Joined {new Date(profile.created_at).toLocaleDateString()}

); } ``` ### Definition of Done - [x] profileService créé avec getProfile et getProfileByUsername - [x] UserProfilePage créé avec rĂ©cupĂ©ration profil - [x] Affichage avatar, username, bio, location, date de crĂ©ation - [x] Gestion Ă©tats loading et error - [x] Route /u/:username ajoutĂ©e - [x] Tests unitaires (coverage ≄ 80%) - [x] Code review approuvĂ© --- ## T0214: Create Update User Profile Frontend Form ✅ **COMPLÉTÉE** **Feature Parente**: FEAT-PROFILE-001 **Phase**: 2 **Priority**: high **Complexity**: medium **Temps EstimĂ©**: 2h **DĂ©pendances**: T0212 ✅, T0213 ✅ **Statut**: ✅ **TERMINÉ** ### Description Technique CrĂ©er formulaire frontend pour mettre Ă  jour profil utilisateur avec validation cĂŽtĂ© client (Zod) et gestion des erreurs. ### Fichiers Ă  CrĂ©er - `apps/web/src/features/profile/components/ProfileEditForm.tsx` - `apps/web/src/features/profile/components/ProfileEditForm.test.tsx` - `apps/web/src/features/profile/schemas/profileSchema.ts` ### Fichiers Ă  Modifier - `apps/web/src/features/profile/services/profileService.ts` (ajouter updateProfile) - `apps/web/src/features/profile/pages/ProfilePage.tsx` (intĂ©grer ProfileEditForm) ### ImplĂ©mentation **Étape 1**: CrĂ©er profileSchema avec Zod (username, bio, etc.) **Étape 2**: CrĂ©er ProfileEditForm avec react-hook-form + Zod **Étape 3**: Ajouter champs: first_name, last_name, username, bio, location, birthdate, gender **Étape 4**: Valider username (3-30 chars, alphanumeric + underscore) **Étape 5**: Valider bio (max 500 chars) **Étape 6**: Valider birthdate (format date, > 13 ans) **Étape 7**: Appeler updateProfile et afficher message succĂšs/erreur ### Code Snippets **apps/web/src/features/profile/schemas/profileSchema.ts**: ```typescript import { z } from 'zod'; export const profileSchema = z.object({ first_name: z.string().max(100).optional(), last_name: z.string().max(100).optional(), username: z.string() .min(3, 'Username must be at least 3 characters') .max(30, 'Username must be at most 30 characters') .regex(/^[a-zA-Z0-9_]+$/, 'Username can only contain letters, numbers, and underscores') .optional(), bio: z.string().max(500, 'Bio must be at most 500 characters').optional(), location: z.string().max(100).optional(), birthdate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format').optional(), gender: z.enum(['Male', 'Female', 'Other', 'Prefer not to say']).optional(), }); export type ProfileFormData = z.infer; ``` **apps/web/src/features/profile/components/ProfileEditForm.tsx**: ```typescript import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { profileSchema, ProfileFormData } from '../schemas/profileSchema'; import { updateProfile } from '../services/profileService'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Textarea } from '@/components/ui/Textarea'; import { Select } from '@/components/ui/Select'; interface ProfileEditFormProps { initialData: Partial; onSuccess?: () => void; } export function ProfileEditForm({ initialData, onSuccess }: ProfileEditFormProps) { const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(profileSchema), defaultValues: initialData, }); const onSubmit = async (data: ProfileFormData) => { try { await updateProfile(data); onSuccess?.(); } catch (error) { console.error('Failed to update profile', error); } }; return (