refactor(docs): réorganiser la documentation selon principes DDD

Réorganise la documentation du projet selon les principes du Domain-Driven Design (DDD) pour améliorer la cohésion, la maintenabilité et l'alignement avec l'architecture modulaire du backend.

**Structure cible:**
```
docs/domains/
├── README.md (Context Map)
├── _shared/ (Core Domain)
├── recommendation/ (Supporting Subdomain)
├── content/ (Supporting Subdomain)
├── moderation/ (Supporting Subdomain)
├── advertising/ (Generic Subdomain)
├── premium/ (Generic Subdomain)
└── monetization/ (Generic Subdomain)
```

**Changements effectués:**

Phase 1: Création de l'arborescence des 7 bounded contexts
Phase 2: Déplacement des règles métier (01-19) vers domains/*/rules/
Phase 3: Déplacement des diagrammes d'entités vers domains/*/entities/
Phase 4: Déplacement des diagrammes flux/états/séquences vers domains/*/
Phase 5: Création des README.md pour chaque domaine
Phase 6: Déplacement des features Gherkin vers domains/*/features/
Phase 7: Création du Context Map (domains/README.md)
Phase 8: Mise à jour de mkdocs.yml pour la nouvelle navigation
Phase 9: Correction automatique des liens internes (script fix-markdown-links.sh)
Phase 10: Nettoyage de l'ancienne structure (regles-metier/, diagrammes/, features/)

**Configuration des tests:**
- Makefile: godog run docs/domains/*/features/
- scripts/generate-bdd-docs.py: features_dir → docs/domains

**Avantages:**
 Cohésion forte: toute la doc d'un domaine au même endroit
 Couplage faible: domaines indépendants, dépendances explicites
 Navigabilité améliorée: README par domaine = entrée claire
 Alignement code/docs: miroir de backend/internal/
 Onboarding facilité: exploration domaine par domaine
 Tests BDD intégrés: features au plus près des règles métier

Voir docs/REFACTOR-DDD.md pour le plan complet.
This commit is contained in:
jpgiannetti
2026-02-07 17:15:02 +01:00
parent 78422bb2c0
commit 5e5fcf4714
227 changed files with 1413 additions and 1967 deletions

View File

@@ -0,0 +1,394 @@
# language: fr
Fonctionnalité: API - Progression et synchronisation audio-guides
En tant que système backend
Je veux sauvegarder et synchroniser la progression des audio-guides
Afin de permettre une reprise fluide et multi-device
Contexte:
Étant donné que l'API RoadWave est démarrée
Et que l'utilisateur "user@example.com" est authentifié
# 16.6.1 - Sauvegarde progression
Scénario: POST /api/v1/audio-guides/{id}/progress - Sauvegarde progression
Étant donné un audio-guide "ag_123" en cours d'écoute
Et que l'utilisateur est à la séquence 3 position 1:42
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress":
"""json
{
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"completed_sequences": ["seq_1", "seq_2"],
"gps_position": {
"latitude": 43.1234,
"longitude": 2.5678
}
}
"""
Alors le code HTTP de réponse est 200
Et la progression est sauvegardée en PostgreSQL
Et le timestamp last_played_at est mis à jour
Et le corps de réponse contient:
"""json
{
"saved": true,
"synced_to_cloud": true,
"updated_at": "2026-01-22T14:35:42Z"
}
"""
Scénario: Sauvegarde automatique toutes les 30 secondes (client)
Étant donné que le client mobile envoie la progression toutes les 30s
Quand je fais un POST sur "/api/v1/audio-guides/{id}/progress"
Alors la progression précédente est écrasée
Et le timestamp est mis à jour
Et la réponse est retournée en < 100ms
Scénario: Sauvegarde des séquences complétées (>80%)
Étant donné qu'une séquence de durée 180 secondes
Et que l'utilisateur a écouté 150 secondes (83%)
Quand la progression est sauvegardée
Alors la séquence est ajoutée à completed_sequences
Et le completion_rate est enregistré à 83%
Scénario: Séquence non marquée complétée si <80%
Étant donné qu'une séquence de durée 222 secondes
Et que l'utilisateur a écouté 90 secondes (40%)
Quand la progression est sauvegardée
Alors la séquence n'est PAS ajoutée à completed_sequences
Et le current_position est sauvegardé (pour reprise)
Scénario: Structure de données progression en PostgreSQL
Étant donné une sauvegarde de progression
Alors la table audio_guide_progress contient:
| champ | type | description |
| id | UUID | ID progression |
| user_id | UUID | ID utilisateur |
| audio_guide_id | UUID | ID audio-guide |
| current_sequence_id | UUID | Séquence en cours |
| current_position | INTEGER | Position en secondes |
| completed_sequences | UUID[] | Tableau séquences complétées |
| last_played_at | TIMESTAMP | Dernière écoute |
| gps_position | GEOGRAPHY | Position GPS optionnelle |
| created_at | TIMESTAMP | Création |
| updated_at | TIMESTAMP | Dernière MAJ |
# 16.6.2 - Reprise progression
Scénario: GET /api/v1/audio-guides/{id}/progress - Récupération progression
Étant donné une progression sauvegardée pour "ag_123"
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"has_progress": true,
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"completed_sequences": ["seq_1", "seq_2"],
"completion_percentage": 25,
"last_played_at": "2026-01-20T14:35:42Z",
"can_resume": true
}
"""
Scénario: Calcul completion_percentage
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur a complété 3 séquences
Quand le pourcentage de complétion est calculé
Alors completion_percentage est 25% (3/12)
Scénario: Progression inexistante (première écoute)
Étant donné qu'aucune progression n'existe pour "ag_456"
Quand je fais un GET sur "/api/v1/audio-guides/ag_456/progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"has_progress": false,
"can_resume": false
}
"""
Scénario: Progression expirée (>30 jours)
Étant donné une progression avec last_played_at à 35 jours
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors can_resume est false
Et le message "Progression expirée après 30 jours" est retourné
Et les données sont conservées mais marquées "expired"
Scénario: DELETE /api/v1/audio-guides/{id}/progress - Réinitialisation
Étant donné une progression existante
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 204
Et la progression est supprimée
Et l'utilisateur peut recommencer depuis le début
# 16.6.3 - Multi-device et synchronisation
Scénario: Synchronisation cloud automatique
Étant donné qu'une progression est sauvegardée sur iPhone
Quand l'utilisateur ouvre l'app sur iPad
Et fait un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors la progression iPhone est récupérée
Et l'utilisateur peut reprendre exactement où il était
Scénario: Conflit de synchronisation (dernier timestamp gagne)
Étant donné une progression sur iPhone avec timestamp "2026-01-22T14:00:00Z"
Et une progression sur iPad avec timestamp "2026-01-22T14:05:00Z"
Quand les deux appareils synchronisent
Alors la progression avec timestamp le plus récent (iPad) est conservée
Et la progression iPhone est écrasée
Scénario: GET /api/v1/audio-guides/progress/sync - Synchronisation batch
Étant donné que l'utilisateur a 5 progressions locales non synchronisées
Quand je fais un POST sur "/api/v1/audio-guides/progress/sync":
"""json
{
"progressions": [
{
"audio_guide_id": "ag_1",
"current_sequence_id": "seq_3",
"current_position_seconds": 102,
"updated_at": "2026-01-22T14:00:00Z"
},
{
"audio_guide_id": "ag_2",
"current_sequence_id": "seq_5",
"current_position_seconds": 45,
"updated_at": "2026-01-22T14:10:00Z"
}
]
}
"""
Alors le code HTTP de réponse est 200
Et toutes les progressions sont synchronisées
Et le corps de réponse contient:
"""json
{
"synced_count": 2,
"conflicts": 0
}
"""
Scénario: Résolution conflit avec notification
Étant donné une progression locale sur iPhone avec timestamp ancien
Et une progression cloud plus récente (depuis iPad)
Quand le sync est effectué
Alors la progression cloud est conservée
Et un conflit est signalé dans la réponse:
"""json
{
"synced_count": 1,
"conflicts": 1,
"conflict_details": [
{
"audio_guide_id": "ag_123",
"cloud_timestamp": "2026-01-22T15:00:00Z",
"local_timestamp": "2026-01-22T14:30:00Z",
"resolution": "cloud_wins"
}
]
}
"""
# Historique et statistiques
Scénario: GET /api/v1/audio-guides/progress/history - Historique écoutes
Étant donné que l'utilisateur a écouté 3 séquences d'un audio-guide
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress/history"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient l'historique:
"""json
{
"listening_sessions": [
{
"sequence_id": "seq_1",
"started_at": "2026-01-22T14:10:00Z",
"completed_at": "2026-01-22T14:12:15Z",
"completion_rate": 100,
"duration_listened": 135
},
{
"sequence_id": "seq_2",
"started_at": "2026-01-22T14:12:20Z",
"completed_at": "2026-01-22T14:14:08Z",
"completion_rate": 100,
"duration_listened": 108
},
{
"sequence_id": "seq_3",
"started_at": "2026-01-22T14:14:15Z",
"completed_at": "2026-01-22T14:17:45Z",
"completion_rate": 92,
"duration_listened": 204
}
],
"total_time_spent": 447
}
"""
Scénario: Détection complétion 100% de l'audio-guide
Étant donné un audio-guide de 12 séquences
Et que l'utilisateur complète la 12ème et dernière séquence à >80%
Quand la progression est sauvegardée
Alors is_completed passe à true
Et completed_at est mis à jour avec le timestamp actuel
Et un événement "audio_guide_completed" est émis
Scénario: GET /api/v1/users/me/audio-guides/completed - Liste des complétés
Étant donné que l'utilisateur a complété 2 audio-guides
Quand je fais un GET sur "/api/v1/users/me/audio-guides/completed"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"completed_count": 2,
"audio_guides": [
{
"audio_guide_id": "ag_1",
"title": "Tour de Paris",
"completed_at": "2026-01-15T10:00:00Z",
"total_sequences": 10,
"total_duration": 3600
},
{
"audio_guide_id": "ag_2",
"title": "Découverte de Lyon",
"completed_at": "2026-01-20T14:00:00Z",
"total_sequences": 8,
"total_duration": 2700
}
]
}
"""
Scénario: GET /api/v1/users/me/audio-guides/in-progress - Liste en cours
Étant donné que l'utilisateur a 3 audio-guides en cours
Quand je fais un GET sur "/api/v1/users/me/audio-guides/in-progress"
Alors le code HTTP de réponse est 200
Et le corps de réponse contient:
"""json
{
"in_progress_count": 3,
"audio_guides": [
{
"audio_guide_id": "ag_123",
"title": "Visite du Louvre",
"current_sequence": 6,
"total_sequences": 12,
"completion_percentage": 50,
"last_played_at": "2026-01-22T14:35:42Z"
}
]
}
"""
# Badges et achievements
Scénario: Attribution badge "Premier audio-guide"
Étant donné qu'un utilisateur complète son 1er audio-guide
Quand le système détecte la complétion
Alors un badge "first_audio_guide" est attribué
Et une notification est envoyée:
"""json
{
"type": "badge_unlocked",
"badge_id": "first_audio_guide",
"title": "🎧 Premier audio-guide",
"message": "Félicitations ! Vous avez complété votre premier audio-guide"
}
"""
Plan du Scénario: Attribution badges selon nombre complétés
Étant donné qu'un utilisateur complète son <nombre>ème audio-guide
Quand le système détecte la complétion
Alors le badge "<badge_id>" est attribué
Exemples:
| nombre | badge_id |
| 1 | first_audio_guide |
| 5 | explorer |
| 10 | completist |
| 25 | expert |
| 50 | master |
# Nettoyage et archivage
Scénario: Archivage progressions inactives (>6 mois)
Étant donné une progression avec last_played_at à 200 jours
Quand le job de nettoyage automatique s'exécute
Alors la progression est déplacée vers la table audio_guide_progress_archive
Et elle reste récupérable via l'API pendant 30 jours supplémentaires
Et après 7 mois total, elle est supprimée définitivement
Scénario: GET /api/v1/audio-guides/{id}/progress/archived - Récupération archivée
Étant donné une progression archivée
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress/archived"
Alors le code HTTP de réponse est 200
Et la progression archivée est retournée
Et un message indique: "Progression archivée. Vous pouvez la restaurer."
Scénario: POST /api/v1/audio-guides/{id}/progress/restore - Restauration
Étant donné une progression archivée
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress/restore"
Alors le code HTTP de réponse est 200
Et la progression est déplacée de archive vers la table active
Et l'utilisateur peut reprendre son écoute
# Cas d'erreur
Scénario: Sauvegarde progression audio-guide inexistant
Quand je fais un POST sur "/api/v1/audio-guides/ag_nonexistant/progress"
Alors le code HTTP de réponse est 404
Et le message d'erreur est "Audio-guide non trouvé"
Scénario: Sauvegarde progression séquence invalide
Étant donné un audio-guide "ag_123" avec 8 séquences
Et une requête avec current_sequence_id "seq_99" (inexistant)
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 400
Et le message d'erreur est "Séquence inexistante pour cet audio-guide"
Scénario: Récupération progression sans authentification
Étant donné une requête sans token JWT
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 401
Et le message d'erreur est "Authentification requise"
Scénario: Corruption de données progression (récupération)
Étant donné une progression avec données corrompues (JSON invalide)
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors le système tente une récupération depuis le backup quotidien
Et si récupération réussie, les données sont restaurées
Et un log d'erreur est créé pour investigation
Scénario: Échec synchronisation cloud (mode dégradé)
Étant donné que la base PostgreSQL est temporairement indisponible
Quand je fais un POST sur "/api/v1/audio-guides/ag_123/progress"
Alors le code HTTP de réponse est 503
Et le message d'erreur est "Service temporairement indisponible. Réessayez dans quelques instants."
Et le client doit conserver la progression localement et réessayer
# Performance et optimisations
Scénario: Index sur user_id + audio_guide_id pour requêtes rapides
Étant donné un index composite (user_id, audio_guide_id)
Quand je fais un GET sur "/api/v1/audio-guides/ag_123/progress"
Alors la requête PostgreSQL utilise l'index
Et le temps de réponse est < 20ms
Scénario: Cache Redis pour progressions actives
Étant donné qu'une progression est fréquemment mise à jour (toutes les 30s)
Quand la progression est sauvegardée
Alors elle est également cachée dans Redis avec TTL 1h
Et les GET suivants lisent depuis Redis (pas PostgreSQL)
Et la latence est < 10ms
Scénario: Invalidation cache Redis lors de réinitialisation
Étant donné qu'une progression est en cache Redis
Quand je fais un DELETE sur "/api/v1/audio-guides/ag_123/progress"
Alors l'entrée cache Redis est supprimée
Et la base PostgreSQL est mise à jour
Et la cohérence est garantie