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,489 @@
# language: fr
Fonctionnalité: API - Notifications géolocalisées et quota anti-spam
En tant qu'API backend
Je veux gérer les notifications de contenus géolocalisés
Afin de respecter les quotas et détecter le bon timing
Contexte:
Étant donné que l'API RoadWave est disponible
Et que Redis est accessible
Et qu'un utilisateur "user123" existe en mode voiture
# Calcul ETA et déclenchement notification
Scénario: API calcule ETA vers point géolocalisé
Étant donné qu'un contenu géolocalisé existe à la position (48.8584, 2.2945) Tour Eiffel
Et que l'utilisateur roule à 50 km/h vers ce point
Et qu'il est actuellement à 98 mètres
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"current_lat": 48.8577,
"current_lon": 2.2950,
"speed_kmh": 50,
"target_content_id": "content_tower"
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 7,
"distance_meters": 98,
"should_notify": true,
"notification_trigger": "eta_threshold"
}
"""
Scénario: API déclenche notification 7 secondes avant arrivée
Étant donné qu'un contenu géolocalisé existe au point GPS
Et que l'ETA calculé est 7 secondes
Quand je POST /api/v1/geo-notifications/trigger
"""json
{
"user_id": "user123",
"content_id": "content_tower",
"eta_seconds": 7
}
"""
Alors le statut de réponse est 201
Et une notification est envoyée au mobile
Et la notification contient:
| champ | valeur |
| type | geo_anchored |
| countdown | 7 |
| icon | 🏛 (selon type contenu) |
| sound | bip court |
Scénario: API ne déclenche pas si ETA >7 secondes
Étant donné qu'un contenu géolocalisé existe
Et que l'ETA calculé est 10 secondes
Quand je POST /api/v1/geo-notifications/check-eta
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 10,
"should_notify": false,
"reason": "eta_above_threshold"
}
"""
Et aucune notification n'est envoyée
Scénario: API déclenche notification immédiate si vitesse faible et proche
Étant donné qu'un contenu géolocalisé existe
Et que l'utilisateur roule à 3 km/h
Et qu'il est à 40 mètres du point
Quand je POST /api/v1/geo-notifications/check-eta
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 0,
"distance_meters": 40,
"speed_kmh": 3,
"should_notify": true,
"notification_trigger": "proximity_and_low_speed"
}
"""
Et une notification est envoyée immédiatement
# Quota anti-spam
Scénario: API vérifie quota avant notification (6 contenus max/heure)
Étant donné que l'utilisateur a déjà reçu 5 notifications géo dans la dernière heure
Quand je POST /api/v1/geo-notifications/check-quota
"""json
{
"user_id": "user123"
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"quota_available": true,
"notifications_count": 5,
"quota_limit": 6,
"remaining": 1
}
"""
Scénario: API refuse notification si quota atteint (6/6)
Étant donné que l'utilisateur a déjà reçu 6 notifications géo dans la dernière heure
Quand je POST /api/v1/geo-notifications/trigger
"""json
{
"user_id": "user123",
"content_id": "content_x"
}
"""
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "QUOTA_EXCEEDED",
"message": "Quota horaire de notifications géolocalisées atteint (6/6)",
"notifications_count": 6,
"quota_limit": 6,
"reset_in_seconds": 1800
}
"""
Et aucune notification n'est envoyée
Scénario: API incrémente quota après notification envoyée
Étant donné que l'utilisateur a reçu 3 notifications géo
Quand je POST /api/v1/geo-notifications/trigger (succès)
Alors le quota passe à 4/6
Et en Redis, la clé "user:user123:geo_quota" contient 4 entrées
Et le sorted set contient les timestamps des 4 notifications
Scénario: API utilise rolling window pour quota horaire
Étant donné que l'utilisateur a reçu 6 notifications:
| timestamp |
| 10:00:00 |
| 10:15:00 |
| 10:30:00 |
| 10:45:00 |
| 11:00:00 |
| 11:05:00 |
Et que l'heure actuelle est 11:10:00
Quand je vérifie le quota
Alors les notifications avant 10:10:00 sont expirées
Et seules 4 notifications sont comptées (10:15, 10:30, 10:45, 11:00, 11:05 = 5)
Et le quota disponible est 1/6
Scénario: API exclut séquences d'audio-guide multi-séquences du quota
Étant donné qu'un audio-guide "Visite Louvre" a 12 séquences
Et que l'utilisateur a écouté les 12 séquences
Quand je calcule le quota
Alors l'audio-guide compte pour 1 seule notification
Et les 12 séquences ne consomment pas 12 quota
Et le quota utilisé est 1/6
# Cooldown après ignorance
Scénario: API active cooldown 10 min si notification ignorée
Étant donné qu'une notification géo a été envoyée à 10:00:00
Et que l'utilisateur n'a pas appuyé sur "Suivant" dans les 7 secondes
Quand je POST /api/v1/geo-notifications/ignored
"""json
{
"user_id": "user123",
"notification_id": "notif123"
}
"""
Alors le statut de réponse est 200
Et un cooldown de 10 minutes est activé
Et en Redis, la clé "user:user123:geo_cooldown" est créée
Et le TTL est 600 secondes (10 minutes)
Scénario: API refuse notification si cooldown actif
Étant donné qu'un cooldown est actif depuis 5 minutes
Et qu'il reste 5 minutes de cooldown
Quand je POST /api/v1/geo-notifications/trigger
Alors le statut de réponse est 429
Et la réponse contient:
"""json
{
"error": "COOLDOWN_ACTIVE",
"message": "Cooldown actif après notification ignorée",
"cooldown_remaining_seconds": 300
}
"""
Et aucune notification n'est envoyée
Scénario: API autorise notification après expiration cooldown
Étant donné qu'un cooldown a été activé à 10:00:00
Et que l'heure actuelle est 10:11:00 (11 minutes après)
Quand je POST /api/v1/geo-notifications/check-quota
Alors le cooldown a expiré
Et les notifications sont à nouveau autorisées
# Tracking GPS temps réel
Scénario: API reçoit positions GPS toutes les secondes
Étant donné que le mobile envoie des positions GPS
Quand je POST /api/v1/gps/track
"""json
{
"user_id": "user123",
"latitude": 48.8577,
"longitude": 2.2950,
"speed_kmh": 50,
"timestamp": "2026-02-02T10:30:00Z"
}
"""
Alors le statut de réponse est 200
Et la position est stockée dans l'historique GPS (Redis)
Et l'historique conserve les 30 derniers points
Scénario: API calcule vitesse moyenne sur 30 derniers points
Étant donné que l'historique GPS contient 30 points
Et que les vitesses enregistrées sont variables
Quand je GET /api/v1/gps/average-speed
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"average_speed_kmh": 48.5,
"samples_count": 30,
"period_seconds": 30
}
"""
Scénario: API détecte contenus géolocalisés dans rayon 500m
Étant donné que l'utilisateur est à la position (48.8577, 2.2950)
Et qu'un contenu géolocalisé existe à 300m
Quand je GET /api/v1/geo-notifications/nearby
"""json
{
"user_id": "user123",
"latitude": 48.8577,
"longitude": 2.2950,
"radius_meters": 500
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"nearby_contents": [
{
"id": "content_tower",
"distance_meters": 300,
"eta_seconds": 21,
"should_notify_soon": false
}
]
}
"""
# Validation contenu géolocalisé
Scénario: API valide que contenu géolocalisé accepté
Étant donné qu'une notification géo a été envoyée
Et que l'utilisateur a appuyé sur "Suivant" dans les 7 secondes
Quand je POST /api/v1/geo-notifications/accepted
"""json
{
"user_id": "user123",
"notification_id": "notif123",
"content_id": "content_tower"
}
"""
Alors le statut de réponse est 200
Et le contenu géolocalisé est inséré en priorité
Et un décompte de 5 secondes est déclenché côté client
Et le quota n'est PAS incrémenté (acceptation = pas de spam)
Scénario: API enregistre contenu géolocalisé perdu
Étant donné qu'une notification géo a été envoyée
Et que l'utilisateur n'a pas appuyé sur "Suivant"
Quand je POST /api/v1/geo-notifications/ignored
Alors le contenu géolocalisé est perdu
Et il n'est PAS inséré dans la file d'attente
Et une métrique "geo_notification_ignored" est enregistrée
Et le cooldown 10 min est activé
# Métriques
Scénario: API stocke métriques de notifications géolocalisées
Étant donné que l'API envoie des notifications géo
Quand je consulte les métriques
Alors les données suivantes sont disponibles:
| métrique | description |
| geo_notifications_sent | Nombre total envoyées |
| geo_notifications_accepted | Nombre acceptées (Suivant) |
| geo_notifications_ignored | Nombre ignorées |
| geo_acceptance_rate | % acceptation |
| average_eta_seconds | ETA moyen au déclenchement |
| quota_exceeded_count | Nombre de refus quota |
# Redis structures
Scénario: API utilise sorted set Redis pour quota
Étant donné que l'utilisateur a reçu 3 notifications
Quand je consulte Redis "user:user123:geo_quota"
Alors la structure est un sorted set:
"""
ZADD user:user123:geo_quota
1707047400 "notif1"
1707047700 "notif2"
1707048000 "notif3"
"""
Et les scores sont les timestamps Unix
Et le TTL est 3600 secondes (1 heure)
Scénario: API compte notifications avec ZCOUNT
Étant donné que le sorted set contient plusieurs notifications
Et que l'heure actuelle est 11:00:00 (timestamp 1707048000)
Quand l'API vérifie le quota
Alors la commande Redis est:
"""
ZCOUNT user:user123:geo_quota 1707044400 1707048000
"""
Et seules les notifications des 60 dernières minutes sont comptées
# Gestion erreurs
Scénario: API gère échec calcul ETA gracieusement
Étant donné qu'une position GPS est invalide
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"current_lat": 200,
"current_lon": 300
}
"""
Alors le statut de réponse est 400
Et la réponse contient:
"""json
{
"error": "INVALID_GPS_COORDINATES",
"message": "Coordonnées GPS invalides"
}
"""
Scénario: API gère vitesse nulle sans crash
Étant donné que l'utilisateur est à l'arrêt (vitesse = 0)
Et qu'un contenu géolocalisé est à 100m
Quand je POST /api/v1/geo-notifications/check-eta
Alors l'ETA est incalculable (vitesse nulle)
Et aucune notification n'est envoyée
Et la réponse contient:
"""json
{
"should_notify": false,
"reason": "speed_zero"
}
"""
Plan du Scénario: Déclenchement selon ETA
Étant donné qu'un contenu géolocalisé existe
Et que l'ETA calculé est <eta> secondes
Quand je vérifie si notification doit être envoyée
Alors la décision est <decision>
Exemples:
| eta | decision |
| 3 | notifier |
| 5 | notifier |
| 7 | notifier |
| 8 | ne pas notifier |
| 10 | ne pas notifier |
| 15 | ne pas notifier |
Plan du Scénario: Quota selon nombre notifications
Étant donné que l'utilisateur a reçu <count> notifications dans l'heure
Quand je vérifie le quota
Alors le quota est <status>
Exemples:
| count | status |
| 0 | disponible |
| 3 | disponible |
| 5 | disponible |
| 6 | atteint |
| 7 | atteint |
# Edge cases : haute vitesse
Scénario: API calcule ETA correctement à 130 km/h
Étant donné qu'un contenu géolocalisé existe
Et que l'utilisateur roule à 130 km/h (36.1 m/s)
Et qu'il est à 252 mètres du point
Quand je POST /api/v1/geo-notifications/check-eta
"""json
{
"user_id": "user123",
"speed_kmh": 130,
"distance_meters": 252
}
"""
Alors le statut de réponse est 200
Et la réponse contient:
"""json
{
"eta_seconds": 7,
"speed_ms": 36.1,
"should_notify": true,
"notification_trigger": "eta_threshold"
}
"""
Et une notification est envoyée 252m avant le point
Scénario: API calcule distance notification selon vitesse
Étant donné qu'un contenu géolocalisé existe
Quand l'utilisateur roule à 180 km/h (50 m/s)
Alors la notification est déclenchée à 350 mètres avant le point
Et après décompte 5s, user a parcouru 250m
Et le contenu démarre 100m avant le point
Et le système fonctionne même à vitesse extrême
# Edge cases : multiples points proches
Scénario: API gère multiples points géolocalisés proches (800m chacun)
Étant donné que 3 châteaux existent espacés de 800m chacun
| contenu | position_km |
| Château A | 0 |
| Château B | 0.8 |
| Château C | 1.6 |
Et que l'utilisateur roule à 50 km/h
Quand une notification Château A est envoyée et acceptée
Alors le quota passe à 1/6
Et aucun cooldown n'est activé (notification acceptée)
Quand 57 secondes s'écoulent (temps pour parcourir 800m)
Et que l'utilisateur atteint Château B
Alors une notification Château B est envoyée
Car le quota n'est pas atteint (1/6)
Et il n'y a pas de cooldown actif
Scénario: API active cooldown réduit après validations multiples
Étant donné que l'utilisateur a validé 2 notifications consécutives
Et que les notifications ont toutes été acceptées (clic "Suivant")
Quand une 3ème notification est ignorée
Alors le cooldown activé est 5 minutes (réduit)
Et non 10 minutes (standard)
Car l'utilisateur a montré de l'engagement précédemment
Scénario: API ignore notifications si cooldown actif après ignorance
Étant donné qu'une notification a été ignorée à 10:00:00
Et qu'un cooldown de 10 minutes est actif
Et que 3 contenus géolocalisés existent à 10:05, 10:08, 10:11
Quand l'utilisateur passe devant le contenu à 10:05
Alors aucune notification n'est envoyée (cooldown actif, reste 5 min)
Quand l'utilisateur passe devant le contenu à 10:08
Alors aucune notification n'est envoyée (cooldown actif, reste 2 min)
Quand l'utilisateur passe devant le contenu à 10:11
Alors une notification est envoyée (cooldown expiré après 10 min)
# Edge cases : mode stationnement
Scénario: API détecte mode stationnement (vitesse < 1 km/h pendant 2 min)
Étant donné que l'utilisateur roule à 50 km/h
Quand la vitesse passe à 0.5 km/h (arrêt complet)
Et que la vitesse reste < 1 km/h pendant 2 minutes consécutives
Alors le mode "stationnement" est activé
Et aucune notification géolocalisée n'est envoyée
Et le système bascule automatiquement en mode piéton
Et un événement "mode_stationnement_detected" est enregistré
Scénario: API sort du mode stationnement quand vitesse > 5 km/h
Étant donné que le mode stationnement est actif depuis 30 minutes
Et que l'utilisateur était à l'arrêt près d'un château
Quand la vitesse passe à 20 km/h pendant 10 secondes
Alors le mode voiture est réactivé
Et les notifications géolocalisées reprennent
Et le quota horaire est vérifié avant nouvelle notification
Scénario: API refuse notification si user arrêté longtemps près d'un point
Étant donné qu'un contenu géolocalisé existe à 30 mètres
Et que l'utilisateur est à l'arrêt (vitesse 0 km/h) depuis 3 minutes
Quand le système détecte la proximité
Alors le mode stationnement est actif
Et aucune notification n'est envoyée
Car l'utilisateur est probablement stationné (parking)
Et pas en mode conduite