19 Commits

Author SHA1 Message Date
jpgiannetti
a2679dd381 chore: supprimer fichiers et dossiers obsolètes
Nettoyage du monorepo en supprimant les éléments inutilisés :
- requirements.txt : dépendances Python obsolètes (Dockerfiles gèrent les dépendances)
- scripts/setup.sh : redondant avec 'make init' et chemins incorrects
- shared/ : placeholder vide jamais utilisé

Mise à jour :
- Makefile docs-clean : ajoute nettoyage du dossier site/ (build MkDocs)
2026-02-12 21:03:21 +01:00
jpgiannetti
23fe67470b docs: migrer schémas BDD de Mermaid vers DBML
Remplace les diagrammes Mermaid par DBML (via kroki-dbml) pour
une meilleure expressivité des schémas de base de données :
- Ajout support notes, contraintes et indexes détaillés
- Migration de tous les schémas d'entités partagées
- Ajout fichier exemple dbml-example.md
- Configuration plugin mkdocs-kroki pour rendu DBML
2026-02-12 20:49:02 +01:00
jpgiannetti
ae2fc3ee6f refactor: réorganiser Dockerfiles et scripts par module
Réorganise la structure Docker pour plus de cohérence dans le monorepo.
Chaque module (backend, docs) a maintenant ses propres Dockerfiles et scripts.

Changements:
- backend/docker/ : Dockerfile (prod) + dev.Dockerfile (hot reload) + init script
- docs/docker/ : mkdocs.Dockerfile + pdf.Dockerfile
- docs/scripts/ : generate-bdd-docs.py + generate-pdf-docs.py
- Déplace docker-compose.yml dans backend/
- Supprime scripts obsolètes (fix-markdown-*.sh, remove-broken-links.sh)
- Déplace .dockerignore à la racine
- Met à jour Makefile avec nouveaux chemins

Organisation finale:
- backend/ : tout ce qui concerne l'API backend
- docs/ : tout ce qui concerne la documentation
- scripts/ : uniquement setup.sh (scripts généraux du projet)
2026-02-12 20:41:10 +01:00
jpgiannetti
35aaa105d0 docs: améliorer rendu markdown et navigation mkdocs
- Ajouter ADR-018 (librairies Go) dans TECHNICAL.md
- Transformer Shared en menu dépliable dans mkdocs (cohérence avec autres domaines)
- Corriger listes markdown (ajout lignes vides avant listes)
- Corriger line breaks dans génération BDD (étapes "Et" sur nouvelles lignes)
- Ajouter script fix-markdown-lists.sh pour corrections futures

Impacte 86 fichiers de documentation et 164 fichiers BDD générés.
2026-02-09 20:49:52 +01:00
jpgiannetti
95c65b8be1 docs: corriger liens cassés vers fichiers supprimés 2026-02-09 19:32:55 +01:00
jpgiannetti
989dc4b6cf Merge branch 'refactor/docs-reorganization' into main 2026-02-08 20:59:35 +01:00
jpgiannetti
64d6611147 docs: réorganiser navigation et corriger génération BDD
- Fusionner Architecture Technique et ADR sous section Architecture unique
- Fermer les menus de navigation par défaut (retrait navigation.expand)
- Ajouter features BDD manquantes pour domaine Shared :
  * Authentication (13 features)
  * Profil (3 features)
  * Partage (2 features)
  * Error Handling (4 features)
- Corriger indentation tableaux dans génération Markdown BDD
  (éviter transformation en blocs de code)
- Corriger diagramme séquence authentification
  (API → DB au lieu de App Mobile → DB)
- Supprimer fichiers obsolètes et index manuels
2026-02-08 20:55:30 +01:00
jpgiannetti
5497ea793f docs: documenter migration et règles de contribution
Ajout de 2 fichiers de documentation :

MIGRATION.md :
- Liste complète des changements effectués
- Tableau de correspondance ancien → nouveau
- Métriques de la migration (21% → 100% features accessibles)
- Guide de rollback si nécessaire
- FAQ

CONTRIBUTING.md :
- Principes fondamentaux (source unique, français, generated/ read-only)
- Guide ajout feature BDD (étape par étape)
- Guide ajout règle métier
- Guide ajout entité et diagrammes
- Conventions de nommage
- Checklist avant commit
- Format des messages de commit
2026-02-08 19:54:30 +01:00
jpgiannetti
a29718a8e8 docs: supprimer index.md manuels du dossier generated/bdd
- Supprimer les 7 fichiers index.md créés manuellement
- Retirer les références 'Vue d'ensemble' dans mkdocs.yml
- Le dossier generated/ doit contenir uniquement les fichiers générés par le script
- Les features BDD restent accessibles directement dans la navigation
2026-02-08 19:52:38 +01:00
jpgiannetti
bc9b3b6a69 docs(bdd): ajouter index.md pour 6 domaines BDD restants
Ajout des pages d'index pour les domaines :
- recommendation (17 features, 4 catégories)
- content (59 features, 6 catégories)
- moderation (23 features, 3 catégories)
- advertising (7 features, 1 catégorie)
- premium (16 features, 3 catégories)
- monetization (8 features, 1 catégorie)

Chaque index contient :
- Description du domaine
- Métriques (features, scénarios, catégories)
- Liste détaillée des catégories avec features principales
- Liens vers rules/ et entities/
- Architecture technique et références ADR
2026-02-08 18:42:15 +01:00
jpgiannetti
20a784b15c docs: intégrer toutes les features BDD dans navigation DDD
Changements :
-  Supprimer section autonome 'Tests BDD' (ligne 229-230)
-  Ajouter section 'Tests BDD' dans les 6 domaines manquants :
  - Recommendation : 17 features (Interest Gauges, Recherche, Recommendation, Search)
  - Content : 59 features (Audio Guides, Content Creation, Navigation, Radio Live, UI)
  - Moderation : 23 features (Admin, API, UI)
  - Advertising : 7 features (Publicites)
  - Premium : 16 features (Abonnements, Mode Offline, Premium)
  - Monetization : 8 features (Monétisation)

Total : 130 features ajoutées (100% des features BDD désormais accessibles)

Navigation : Domaine → Rules → Entities → Tests BDD (source unique)
2026-02-08 18:38:43 +01:00
jpgiannetti
3da7eb80a1 docs: mettre à jour liens après renommages
- Mise à jour de tous les liens dans les fichiers .md
- Mise à jour de la navigation dans mkdocs.yml
- Tous les chemins pointent vers les nouveaux noms en français
2026-02-08 18:18:28 +01:00
jpgiannetti
62fe0ed5eb docs: renommer fichiers EN→FR pour cohérence linguistique
Entités:
- entities-overview.md → vue-ensemble.md

États (Lifecycles):
- user-account-lifecycle.md → compte-utilisateur.md
- content-lifecycle.md → contenu.md
- session-lifecycle.md → session.md
- report-lifecycle.md → signalement.md
- export-lifecycle.md → export-donnees.md
- parental-consent-lifecycle.md → consentement-parental.md
- account-deletion-lifecycle.md → suppression-compte.md
- breach-incident-lifecycle.md → incident-breach.md

Séquences:
- authentication-flow.md → authentification.md
- token-refresh.md → refresh-token.md (terme technique conservé)
- content-moderation.md → moderation-contenu.md
- content-report.md → signalement.md
2026-02-08 18:17:48 +01:00
jpgiannetti
4b28db3465 docs: fusionner séquences dupliquées (garder versions FR)
- Supprimer data-export.md (doublon de export-donnees.md)
- Supprimer account-deletion.md (doublon de suppression-compte.md)
- Mettre à jour mkdocs.yml pour supprimer les doublons
- Garder uniquement les versions françaises plus détaillées
2026-02-08 18:17:30 +01:00
jpgiannetti
b52ffb8db9 feat(rgpd): compléter documentation RGPD avec 12 nouvelles sections
Règles RGPD (docs/domains/_shared/rules/rgpd.md):
- Ajouter sections 13.11-13.22 (droits utilisateurs, mineurs, sécurité)
- Droit de rectification, opposition, limitation du traitement
- Gestion des mineurs: 13 ans minimum + consentement parental 13-15 ans
- Protection renforcée: RoadWave Kids pour < 13 ans
- Sécurité: chiffrement multi-niveaux, procédure breach 72h CNIL
- Politique de confidentialité avec versioning
- Sous-traitants, DPIA, délais de réponse

Entités (6 nouvelles):
- PARENTAL_CONSENTS + PARENTAL_CONTROLS (workflow 13-15 ans)
- PRIVACY_POLICY_VERSIONS + USER_POLICY_ACCEPTANCES
- ACCOUNT_DELETIONS (grace period 30j)
- BREACH_INCIDENTS + BREACH_AFFECTED_USERS
- USER_PROFILE_HISTORY (audit trail rectification)
- DATA_RETENTION_LOGS (purge 5 ans)

Diagrammes séquences (5 nouveaux):
- Consentement parental avec validation email
- Anonymisation GPS automatique après 24h
- Notification breach CNIL (procédure 72h)
- Export données asynchrone
- Suppression compte avec grace period

Cycles de vie (3 nouveaux + 1 enrichi):
- parental-consent-lifecycle.md
- breach-incident-lifecycle.md
- account-deletion-lifecycle.md
- user-account-lifecycle.md (ajout états mineurs, frozen)

Features BDD (4 nouvelles, 195 scénarios RGPD):
- minors-protection.feature (9 scénarios)
- data-security.feature (12 scénarios)
- privacy-policy.feature (8 scénarios)
- user-rights.feature (8 scénarios)

Infrastructure:
- Réorganiser docs générées: docs/bdd + output → generated/bdd + generated/pdf
- Mettre à jour mkdocs.yml, Makefile, scripts Python
- Ajouter /generated/ au .gitignore
2026-02-08 17:54:46 +01:00
jpgiannetti
fd2b0f70c5 feat(rgpd): compléter documentation RGPD avec 12 nouvelles sections
Règles RGPD (docs/domains/_shared/rules/rgpd.md):
- Ajouter sections 13.11-13.22 (droits utilisateurs, mineurs, sécurité)
- Droit de rectification, opposition, limitation du traitement
- Gestion des mineurs: 13 ans minimum + consentement parental 13-15 ans
- Protection renforcée: RoadWave Kids pour < 13 ans
- Sécurité: chiffrement multi-niveaux, procédure breach 72h CNIL
- Politique de confidentialité avec versioning
- Sous-traitants, DPIA, délais de réponse

Entités (6 nouvelles):
- PARENTAL_CONSENTS + PARENTAL_CONTROLS (workflow 13-15 ans)
- PRIVACY_POLICY_VERSIONS + USER_POLICY_ACCEPTANCES
- ACCOUNT_DELETIONS (grace period 30j)
- BREACH_INCIDENTS + BREACH_AFFECTED_USERS
- USER_PROFILE_HISTORY (audit trail rectification)
- DATA_RETENTION_LOGS (purge 5 ans)

Diagrammes séquences (5 nouveaux):
- Consentement parental avec validation email
- Anonymisation GPS automatique après 24h
- Notification breach CNIL (procédure 72h)
- Export données asynchrone
- Suppression compte avec grace period

Cycles de vie (3 nouveaux + 1 enrichi):
- parental-consent-lifecycle.md
- breach-incident-lifecycle.md
- account-deletion-lifecycle.md
- user-account-lifecycle.md (ajout états mineurs, frozen)

Features BDD (4 nouvelles, 195 scénarios RGPD):
- minors-protection.feature (9 scénarios)
- data-security.feature (12 scénarios)
- privacy-policy.feature (8 scénarios)
- user-rights.feature (8 scénarios)

Infrastructure:
- Réorganiser docs générées: docs/bdd + output → generated/bdd + generated/pdf
- Mettre à jour mkdocs.yml, Makefile, scripts Python
- Ajouter /generated/ au .gitignore
2026-02-08 17:49:12 +01:00
jpgiannetti
1896c249dd fix(docs): corriger liens cassés vers entities-overview.md (anciennement modele-global.md) 2026-02-08 10:34:40 +01:00
jpgiannetti
e63603551d docs(shared): ajouter documentation complète entités, états et séquences
- Entités: 7 nouveaux schémas (sessions, devices, consents, location-history, interest-gauges, reports, exports)
- États: 5 diagrammes lifecycles (compte, contenu, session, signalement, export)
- Séquences: 6 flows (auth, refresh token, modération, signalement, export, suppression)
- Renommage: modele-global.md → entities-overview.md
- MkDocs: organisation hiérarchique par catégories

Format concis: diagrammes Mermaid + règles essentielles uniquement
2026-02-07 21:38:02 +01:00
jpgiannetti
cf7a46be27 refactor(docs): réorganiser structure documentation
- Déplacer TECHNICAL.md vers docs/ pour cohérence
- Renommer docs/index.md en docs/README.md (convention GitHub)
- Créer docs/adr/README.md comme index des ADR
- Supprimer docs/REFACTOR-DDD.md (plan appliqué)
- Supprimer docs/technical.md (doublon)
- Mettre à jour tous les liens internes
- Mettre à jour mkdocs.yml pour nouvelle structure

Structure finale :
- README.md : vue d'ensemble projet (GitHub)
- docs/README.md : page d'accueil documentation (MkDocs)
- docs/TECHNICAL.md : architecture technique
- docs/adr/README.md : index des 25 ADR
2026-02-07 19:56:40 +01:00
138 changed files with 4695 additions and 82721 deletions

31
.dockerignore Normal file
View File

@@ -0,0 +1,31 @@
# Ignore backend files
backend/**
mobile/**
# Ignore git
.git/
.gitignore
# Ignore docker files
docker-compose.yml
*.dockerfile
# Ignore node_modules and build artifacts
node_modules/
dist/
build/
bin/
tmp/
# Ignore environment files
.env
.env.*
# Ignore IDE
.vscode/
.idea/
# Ignore documentation build artifacts
site/
docs/generated/
output/

4
.gitignore vendored
View File

@@ -64,4 +64,6 @@ config/*.local.yaml
# MkDocs
site/
.cache/
docs/bdd/
# Generated documentation
docs/generated/

View File

@@ -137,6 +137,10 @@ make docs-pdf # Generate PDF of all documentation
make docs-clean # Remove generated docs and PDF
```
**First run**: The first `make docs-serve` will build a custom Docker image with mkdocs-kroki-plugin. This takes ~30s but is cached for subsequent runs.
**DBML Support**: The documentation now supports DBML (Database Markup Language) for database diagrams via the Kroki plugin. Use `kroki-dbml` code blocks in markdown files. See [docs/examples/dbml-example.md](docs/examples/dbml-example.md) for examples.
## Working with sqlc
When adding or modifying database queries:
@@ -288,7 +292,7 @@ Zitadel handles authentication ([ADR-008](docs/adr/008-authentification.md)):
## Performance Targets
See [TECHNICAL.md](TECHNICAL.md) for detailed metrics:
See [TECHNICAL.md](docs/TECHNICAL.md) for detailed metrics:
- API latency p99: < 100ms
- Audio start time: < 3s
- Target availability: 99.9%

View File

@@ -70,14 +70,14 @@ clean:
## docs-clean: Remove generated documentation (BDD docs and PDF)
docs-clean:
@echo "$(YELLOW)Cleaning generated documentation...$(NC)"
@rm -rf docs/bdd/ output/RoadWave_Documentation.pdf
@docker rmi roadwave-pdf-generator 2>/dev/null || true
@rm -rf docs/generated/ site/
@docker rmi roadwave-pdf-generator roadwave-mkdocs 2>/dev/null || true
@echo "$(GREEN)✓ Documentation cleaned$(NC)"
## docker-up: Start all Docker services
docker-up:
@echo "$(BLUE)Starting Docker services...$(NC)"
@docker compose up -d
@cd backend && docker compose up -d
@echo "$(GREEN)✓ Services started$(NC)"
@echo "$(YELLOW)API: http://localhost:8080$(NC)"
@echo "$(YELLOW)Zitadel: http://localhost:8081$(NC)"
@@ -86,12 +86,12 @@ docker-up:
## docker-down: Stop all Docker services
docker-down:
@echo "$(YELLOW)Stopping Docker services...$(NC)"
@docker compose down
@cd backend && docker compose down
@echo "$(GREEN)✓ Services stopped$(NC)"
## docker-logs: Show Docker logs
docker-logs:
@docker compose logs -f
@cd backend && docker compose logs -f
## migrate-up: Apply all migrations
migrate-up:
@@ -151,30 +151,34 @@ run-api:
## docs-serve: Start documentation server (http://localhost:8000)
docs-serve:
@echo "$(BLUE)Building MkDocs Docker image with plugins...$(NC)"
@docker build -t roadwave-mkdocs -f docs/docker/mkdocs.Dockerfile . -q
@echo "$(BLUE)Generating BDD documentation from Gherkin files...$(NC)"
@python3 scripts/generate-bdd-docs.py
@python3 docs/scripts/generate-bdd-docs.py
@echo "$(GREEN)✓ BDD documentation generated$(NC)"
@echo "$(BLUE)Starting documentation server...$(NC)"
@echo "$(YELLOW)Documentation available at http://localhost:8000$(NC)"
@docker run --rm -it -p 8000:8000 -v "$(PWD):/docs" squidfunk/mkdocs-material
@docker run --rm -p 8000:8000 -v "$(PWD):/docs" roadwave-mkdocs
## bdd-docs: Generate BDD docs from Gherkin and serve with MkDocs (http://localhost:8000)
bdd-docs:
@echo "$(BLUE)Building MkDocs Docker image with plugins...$(NC)"
@docker build -t roadwave-mkdocs -f docs/docker/mkdocs.Dockerfile . -q
@echo "$(BLUE)Generating BDD documentation from Gherkin files...$(NC)"
@python3 scripts/generate-bdd-docs.py
@python3 docs/scripts/generate-bdd-docs.py
@echo "$(GREEN)✓ BDD documentation generated$(NC)"
@echo "$(BLUE)Starting documentation server...$(NC)"
@echo "$(YELLOW)Documentation available at http://localhost:8000$(NC)"
@echo "$(YELLOW)Navigate to 'Tests BDD' section$(NC)"
@docker run --rm -it -p 8000:8000 -v "$(PWD):/docs" squidfunk/mkdocs-material
@docker run --rm -p 8000:8000 -v "$(PWD):/docs" roadwave-mkdocs
## docs-pdf: Generate PDF of all documentation (output/RoadWave_Documentation.pdf)
docs-pdf:
@echo "$(BLUE)Generating BDD documentation from Gherkin files...$(NC)"
@python3 scripts/generate-bdd-docs.py
@python3 docs/scripts/generate-bdd-docs.py
@echo "$(GREEN)✓ BDD documentation generated$(NC)"
@echo "$(BLUE)Building PDF generator Docker image...$(NC)"
@docker build -t roadwave-pdf-generator -f scripts/Dockerfile.pdf . -q
@docker build -t roadwave-pdf-generator -f docs/docker/pdf.Dockerfile . -q
@echo "$(BLUE)Generating PDF documentation...$(NC)"
@docker run --rm -u $(shell id -u):$(shell id -g) -v "$(PWD):/docs" roadwave-pdf-generator
@echo "$(GREEN)✓ PDF generated: output/RoadWave_Documentation.pdf$(NC)"

View File

@@ -1,228 +0,0 @@
# RoadWave - Architecture Technique
> Les décisions techniques sont documentées dans [docs/adr/](docs/adr/)
## Stack Technologique
| Composant | Technologie | ADR |
|-----------|-------------|-----|
| **Backend** | Go + Fiber | [ADR-001](docs/adr/001-langage-backend.md) |
| **Architecture Backend** | Monolithe Modulaire | [ADR-010](docs/adr/010-architecture-backend.md) |
| **Authentification** | Zitadel (self-hosted OVH) | [ADR-008](docs/adr/008-authentification.md) |
| **Streaming** | HLS | [ADR-002](docs/adr/002-protocole-streaming.md) |
| **Codec** | Opus | [ADR-003](docs/adr/003-codec-audio.md) |
| **CDN** | NGINX Cache (OVH VPS) | [ADR-004](docs/adr/004-cdn.md) |
| **Storage** | OVH Object Storage | [ADR-004](docs/adr/004-cdn.md) |
| **Hébergement MVP** | OVH VPS Essential | [ADR-015](docs/adr/015-hebergement.md) |
| **Organisation** | Monorepo | [ADR-014](docs/adr/014-organisation-monorepo.md) |
| **Base de données** | PostgreSQL + PostGIS | [ADR-005](docs/adr/005-base-de-donnees.md) |
| **ORM/Accès données** | sqlc | [ADR-011](docs/adr/011-orm-acces-donnees.md) |
| **Cache** | Redis Cluster | [ADR-021](docs/adr/021-solution-cache.md) |
| **Chiffrement** | TLS 1.3 | [ADR-006](docs/adr/006-chiffrement.md) |
| **Live** | WebRTC | [ADR-002](docs/adr/002-protocole-streaming.md) |
| **Frontend Mobile** | Flutter | [ADR-012](docs/adr/012-frontend-mobile.md) |
| **Tests** | Testify + Godog (Gherkin) | [ADR-013](docs/adr/013-strategie-tests.md), [ADR-007](docs/adr/007-tests-bdd.md) |
| **Paiements** | Mangopay | [ADR-009](docs/adr/009-solution-paiement.md) |
| **Emailing** | Brevo | [ADR-016](docs/adr/016-service-emailing.md) |
| **Géolocalisation IP** | IP2Location (fallback) | [ADR-019](docs/adr/019-geolocalisation-ip.md) |
| **Librairies Mobile** | Flutter packages | [ADR-020](docs/adr/020-librairies-flutter.md) |
| **CI/CD** | GitHub Actions (monorepo) | [ADR-022](docs/adr/022-strategie-cicd-monorepo.md) |
| **Modération** | Architecture modération | [ADR-023](docs/adr/023-architecture-moderation.md) |
| **Monitoring** | Prometheus + Grafana | [ADR-024](docs/adr/024-monitoring-observabilite.md) |
| **Secrets** | Vault + sealed secrets | [ADR-025](docs/adr/025-securite-secrets.md) |
| **Notifications géo** | Push + geofencing | [ADR-017](docs/adr/017-notifications-geolocalisees.md) |
| **Notifications push** | FCM + APNS | [ADR-018](docs/adr/018-notifications-push.md) |
---
## Streaming Audio
### Protocole : HLS (HTTP Live Streaming)
- Fonctionne à travers firewalls et réseaux mobiles instables
- Cache CDN natif (réduction des coûts)
- Bitrate adaptatif automatique (tunnels, zones rurales)
- Support natif iOS/Android
### Codec : Opus
Optimisé pour la voix en environnement bruyant (voiture).
| Qualité | Bitrate | Usage |
|---------|---------|-------|
| Basse | 24 kbps | 2G/Edge |
| Standard | 48 kbps | 3G |
| Haute | 64 kbps | 4G/5G |
Fallback AAC-LC pour appareils legacy.
### Buffering Adaptatif
| Réseau | Buffer min | Buffer cible | Buffer max |
|--------|------------|--------------|------------|
| WiFi | 5s | 30s | 120s |
| 4G/5G | 10s | 45s | 120s |
| 3G | 30s | 90s | 300s |
---
## Sécurité
### Chiffrement
- **TLS 1.3** sur tous les endpoints (overhead ~1-2%)
- **DTLS-SRTP** pour WebRTC (radio live)
- Pas de DRM initialement (ajout si licences l'exigent)
### Authentification
- **Zitadel self-hosted sur OVH France** (Gravelines) pour IAM
- Souveraineté totale : 100% données en France (cohérent avec ADR-004)
- JWT validation locale (zitadel-go SDK)
- OAuth2 PKCE pour mobile (iOS/Android)
- MFA et passkeys disponibles
- Rate limiting par IP et par utilisateur (Nginx + Zitadel)
- PostgreSQL schema partagé avec RoadWave (séparation logique)
---
## Base de Données
### PostgreSQL + PostGIS
```sql
-- Requête géolocalisée typique
SELECT id, ST_Distance(location::geography, ST_MakePoint($lon, $lat)::geography) as distance
FROM contents
WHERE ST_DWithin(location::geography, ST_MakePoint($lon, $lat)::geography, 50000)
ORDER BY distance
LIMIT 20;
```
### Redis Geospatial (Cache)
```
GEOADD contents:geo longitude latitude content_id
GEORADIUS contents:geo user_lon user_lat 50 km WITHDIST COUNT 20 ASC
```
TTL cache : 5 minutes (le contenu ne bouge pas).
---
## Architecture Services
```mermaid
flowchart TB
subgraph clients["Clients"]
mobile["Mobile Apps<br/>iOS/Android<br/>Flutter"]
carplay["CarPlay /<br/>Android Auto"]
end
subgraph ovh["OVH VPS Essential (Gravelines, France)"]
nginx["NGINX Cache<br/>+ Let's Encrypt<br/>TLS 1.3, Rate Limiting"]
api["API Gateway<br/>Go + Fiber :8080"]
subgraph services["Backend Services (Monolithe Modulaire)"]
auth["Auth Service<br/>JWT validation"]
user["User Service<br/>Profils, Jauges"]
content["Content/Geo Service<br/>Recommandations<br/>PostGIS queries"]
streaming["Streaming Service<br/>HLS generation"]
payment["Payment Service<br/>Mangopay integration"]
notif["Notification Service<br/>FCM/APNS"]
end
zitadel["Zitadel IdP<br/>OAuth2 PKCE<br/>:8081"]
ip2loc["IP2Location DB<br/>SQLite ~50MB<br/>Mode dégradé"]
subgraph data["Données"]
pgbouncer["PgBouncer<br/>Connection pooling<br/>:6432"]
postgres["PostgreSQL 16<br/>+ PostGIS 3.4<br/>Schémas:<br/>- roadwave<br/>- zitadel"]
redis["Redis 7 Cluster<br/>Cache + Geospatial<br/>GEORADIUS"]
end
end
subgraph external["Services Externes"]
storage["OVH Object Storage<br/>Fichiers audio HLS"]
mangopay["Mangopay<br/>Paiements, KYC"]
brevo["Brevo<br/>Emails transactionnels"]
fcm["FCM / APNS<br/>Push notifications"]
end
mobile --> nginx
carplay --> nginx
nginx --> api
api --> auth
api --> user
api --> content
api --> streaming
api --> payment
api --> notif
api --> ip2loc
auth --> zitadel
user --> pgbouncer
user --> redis
content --> pgbouncer
content --> redis
streaming --> storage
payment --> mangopay
notif --> fcm
zitadel --> pgbouncer
pgbouncer --> postgres
brevo -.email.-> mobile
fcm -.push.-> mobile
style ovh fill:#e3f2fd
style external fill:#fff3e0
style clients fill:#f3e5f5
style data fill:#e8f5e9
```
**Souveraineté** : 100% données en France (RGPD compliant)
---
## Scaling 10M Utilisateurs
### Stratégie par phase
| Phase | Utilisateurs | Infra | Coût estimé |
|-------|--------------|-------|-------------|
| MVP | 0-20K | OVH VPS Essential + PostgreSQL + Zitadel + NGINX Cache | ~14€/mois |
| Growth | 20K-500K | Scaleway Instances (multi-replicas), OVH Object Storage | 150-500€/mois |
| Scale | 500K+ | Multi-région, Kubernetes managé, NGINX origin shield | 2-10K€/mois |
### Métriques cibles
| Métrique | Objectif |
|----------|----------|
| Latence API p99 | < 100ms |
| Temps de démarrage audio | < 3s |
| Disponibilité | 99.9% |
| Connexions/serveur | 100K+ |
---
## Points de vigilance
1. **Buffering mobile** : Pré-chargement agressif avant tunnels (détection GPS)
2. **Handoff réseau** : Buffer suffisant pour survivre aux changements de cellule
3. **Mode offline** : Téléchargement complet sur WiFi
4. **Bande passante** : 48 kbps Opus = ~20 MB/heure (faible consommation data)
---
## Pourquoi pas UDP brut ?
| UDP | HLS/TCP |
|-----|---------|
| Latence minimale | Latence acceptable (5-30s) |
| Problèmes NAT/firewall | Passe partout |
| Perte de paquets = artefacts | Retransmission automatique |
| Pas de cache CDN | Cache CDN = économies |
| Complexité++ | Standard de l'industrie |
Pour du contenu non-interactif (podcasts, audio-guides), la latence HLS est acceptable. WebRTC réservé à la radio live uniquement.

View File

@@ -4,15 +4,15 @@ services:
# Backend API
api:
build:
context: ./backend
dockerfile: Dockerfile.dev
context: .
dockerfile: docker/dev.Dockerfile
ports:
- "8080:8080"
volumes:
- ./backend:/app
- .:/app
- /app/tmp
env_file:
- ./backend/.env
- ./.env
depends_on:
postgres:
condition: service_healthy

462
docs/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,462 @@
# Guide de Contribution à la Documentation
Bienvenue ! Ce guide explique comment contribuer à la documentation RoadWave tout en respectant son architecture et ses conventions.
## Principes Fondamentaux
### 1. Source Unique de Vérité (Single Source of Truth)
Chaque information doit exister à **un seul endroit** :
- **Règles métier** → `docs/domains/[domain]/rules/*.md`
- **Features BDD** → `domains/[domain]/features/**/*.feature` (sources Gherkin)
- **Documentation BDD** → `docs/generated/bdd/` (générée automatiquement)
- **Entités** → `docs/domains/[domain]/entities/*.md`
- **États** → `docs/domains/[domain]/states/*.md`
- **Séquences** → `docs/domains/[domain]/sequences/*.md`
**Ne jamais** dupliquer une information
**Toujours** référencer via des liens
### 2. Langue : 100% Français
Tous les fichiers de documentation doivent être en français :
- ✅ Noms de fichiers en français (kebab-case)
- ✅ Contenu en français
- ✅ Navigation `mkdocs.yml` en français
- ⚠️ Exception : code backend en anglais (convention internationale)
### 3. Dossier `generated/` = Read-Only
Le dossier `docs/generated/` contient **exclusivement** des fichiers générés automatiquement.
**Ne JAMAIS** créer de fichiers manuels dans `generated/`
**Ne JAMAIS** éditer des fichiers dans `generated/`
✅ Modifier les sources (`.feature`) et regénérer
## Ajouter une Nouvelle Feature BDD
### Étape 1 : Créer le fichier `.feature`
```bash
# Créer dans le bon domaine
touch domains/[domain]/features/[category]/ma-feature.feature
```
**Exemple** : Feature de recherche dans le domaine recommendation
```bash
touch domains/recommendation/features/recherche/recherche-avancee.feature
```
### Étape 2 : Écrire la feature en Gherkin
```gherkin
# language: fr
Fonctionnalité: Recherche avancée de contenus
En tant qu'utilisateur
Je veux rechercher des contenus avec des filtres avancés
Afin de trouver exactement ce que je cherche
Contexte:
Étant donné que je suis connecté
Et que je suis sur la page de recherche
Scénario: Recherche par catégorie et distance
Quand je sélectionne la catégorie "Tourisme"
Et je définis un rayon de 10 km
Alors je vois uniquement les contenus de type "Tourisme"
Et tous les résultats sont à moins de 10 km
Scénario: Recherche avec filtres multiples
# ... autres scénarios
```
**Conventions Gherkin** :
- Langue française (`# language: fr`)
- Mots-clés : `Fonctionnalité`, `Scénario`, `Étant donné`, `Quand`, `Alors`
- Commentaires pour référencer les règles DDD
### Étape 3 : Regénérer la documentation
```bash
make bdd-docs
```
Cette commande :
1. Parse les fichiers `.feature`
2. Génère les `.md` dans `generated/bdd/`
3. Démarre le serveur MkDocs (http://localhost:8000)
### Étape 4 : Ajouter dans `mkdocs.yml`
Si la catégorie n'existe pas encore, l'ajouter :
```yaml
- '🎯 Recommendation':
# ...
- Tests BDD:
- 'Recherche':
- Recherche de contenu: generated/bdd/recommendation/features/recherche/recherche.md
- Recherche avancée: generated/bdd/recommendation/features/recherche/recherche-avancee.md # ← NOUVEAU
```
### Étape 5 : Commit
```bash
git add domains/recommendation/features/recherche/recherche-avancee.feature
git add mkdocs.yml
git commit -m "feat(bdd): ajouter feature recherche avancée
- Ajout recherche par catégorie et distance
- Ajout filtres multiples
- Couverture domaine recommendation"
```
## Ajouter une Règle Métier
### Étape 1 : Identifier le domaine
Déterminer dans quel domaine DDD se situe la règle :
- **Shared** : Authentification, RGPD, erreurs, profil
- **Recommendation** : Algorithme, jauges d'intérêt, recherche
- **Content** : Audio-guides, création, streaming
- **Moderation** : Signalements, validation, sanctions
- **Advertising** : Publicités, ciblage
- **Premium** : Abonnements, mode offline
- **Monetization** : Paiements, revenus créateurs
### Étape 2 : Créer ou éditer le fichier de règles
```bash
# Éditer fichier existant
vim domains/[domain]/rules/[theme].md
# Ou créer un nouveau fichier
touch domains/[domain]/rules/nouveau-theme.md
```
### Étape 3 : Documenter la règle
```markdown
# Règles - Nouveau Thème
## Contexte
Description du contexte métier...
## Règles
### Règle 1 : Titre de la règle
**Contexte** : Quand...
**Règle** : L'utilisateur doit/peut/ne peut pas...
**Justification** : Parce que...
**Exemple** :
- Cas nominal : ...
- Cas d'erreur : ...
### Règle 2 : Autre règle
...
## Liens avec d'autres domaines
- [Entité X](../entities/entite-x.md)
- [Séquence Y](../sequences/sequence-y.md)
- [ADR-XXX](../../adr/XXX-decision.md)
```
### Étape 4 : Créer les features BDD correspondantes
Les règles métier doivent être testables → créer des features BDD.
### Étape 5 : Ajouter dans `mkdocs.yml`
```yaml
- '🎙️ Content':
- Règles:
- Création & Publication: domains/content/rules/creation-publication.md
- Nouveau Thème: domains/content/rules/nouveau-theme.md # ← NOUVEAU
```
## Ajouter une Entité
### Créer le fichier
```bash
touch domains/[domain]/entities/nouvelle-entite.md
```
### Documenter l'entité
```markdown
# Entité : NouvelleThing
## Description
Brève description de l'entité...
## Attributs
| Attribut | Type | Description | Contraintes |
|----------|------|-------------|-------------|
| `id` | UUID | Identifiant unique | PK, NOT NULL |
| `name` | String | Nom de la chose | NOT NULL, max 255 |
| `created_at` | Timestamp | Date de création | NOT NULL |
## Relations
- **BelongsTo** : [User](../../../_shared/entities/users.md)
- **HasMany** : [Items](./items.md)
## États du Cycle de Vie
Voir [Lifecycle](../states/nouvelle-thing-lifecycle.md)
## Règles Métier
- [Règle de création](../rules/creation.md#nouvelle-thing)
- [Règle de validation](../rules/validation.md#nouvelle-thing)
## Implémentation Backend
**Module** : `backend/internal/[domain]/`
**Table** : `nouvelle_things`
**Repository** : `nouvelle_thing_repository.go`
Requêtes sqlc : `backend/queries/nouvelle_thing.sql`
```
### Ajouter dans `mkdocs.yml`
```yaml
- Entités:
- Vue d'ensemble: domains/[domain]/entities/vue-ensemble.md
- Nouvelle Entité: domains/[domain]/entities/nouvelle-entite.md # ← NOUVEAU
```
## Ajouter un Diagramme
### Diagrammes de Séquence
```bash
touch domains/[domain]/sequences/nouveau-processus.md
```
```markdown
# Séquence - Nouveau Processus
## Diagramme
\`\`\`mermaid
sequenceDiagram
participant U as Utilisateur
participant A as API
participant DB as Database
U->>A: POST /nouvelle-action
A->>DB: Vérifier permissions
DB-->>A: OK
A->>DB: INSERT nouvelle_thing
A-->>U: 201 Created
\`\`\`
## Légende
**Acteurs** :
- Utilisateur : Client de l'API
- API : Backend RoadWave
- Database : PostgreSQL
**Étapes clés** :
1. Vérification des permissions
2. Insertion en base
3. Retour confirmation
## Cas d'Erreur
### Permissions insuffisantes
...
### Contrainte violée
...
```
### Diagrammes d'États
```bash
touch domains/[domain]/states/nouvelle-thing-lifecycle.md
```
```markdown
# États - Lifecycle NouvelleThing
## Diagramme
\`\`\`mermaid
stateDiagram-v2
[*] --> Draft
Draft --> Pending: Submit
Pending --> Approved: Approve
Pending --> Rejected: Reject
Approved --> Published: Publish
Published --> Archived: Archive
Archived --> [*]
\`\`\`
## États
| État | Description | Transitions Possibles |
|------|-------------|----------------------|
| Draft | Création en cours | → Pending |
| Pending | En attente validation | → Approved, Rejected |
| Approved | Validé | → Published |
| Rejected | Refusé | → Draft (correction) |
| Published | Publié | → Archived |
| Archived | Archivé | (final) |
## Règles de Transition
### Draft → Pending
**Conditions** : Tous les champs obligatoires remplis
**Actions** : Notification modérateurs
```
## Conventions de Nommage
### Fichiers Markdown
```bash
# Format : kebab-case-francais.md
✅ export-donnees.md
✅ suppression-compte.md
✅ moderation-communautaire.md
✅ vue-ensemble.md
❌ data-export.md (anglais)
❌ entities-overview.md (anglais)
❌ ExportDonnees.md (PascalCase)
❌ export_donnees.md (snake_case)
```
### Fichiers Features
```bash
# Format : kebab-case-francais.feature
✅ signalement.feature
✅ jauge-initiale.feature
✅ creation-audio-guide.feature
❌ reporting.feature (anglais)
❌ initial-gauge.feature (anglais)
```
### Répertoires
```bash
# Format : kebab-case-anglais (convention internationale)
✅ features/
✅ interest-gauges/
✅ content-creation/
✅ rgpd-compliance/
```
## Vérifications Avant Commit
### Checklist
- [ ] Tous les fichiers sont en français (sauf code backend)
- [ ] Pas de fichiers dans `generated/` (seulement sources modifiées)
- [ ] Les liens internes fonctionnent
- [ ] `mkdocs.yml` mis à jour si nécessaire
- [ ] Features BDD regénérées (`make bdd-docs`)
- [ ] Message de commit descriptif
### Tester Localement
```bash
# Vérifier syntaxe YAML
python3 -c "import yaml; yaml.safe_load(open('mkdocs.yml'))"
# Tester la navigation
make docs-serve
# → http://localhost:8000
```
### Vérifier les Liens
```bash
# Chercher liens cassés vers anciens noms
grep -r "entities-overview\.md" docs/
grep -r "user-account-lifecycle\.md" docs/
grep -r "data-export\.md" docs/
# Ne devrait rien retourner
```
## Messages de Commit
### Format
```
type(scope): description courte
Corps optionnel avec détails...
Refs: #issue-number
```
### Types
- `feat(bdd)` : Nouvelle feature BDD
- `docs(rules)` : Nouvelle règle métier
- `docs(entity)` : Nouvelle entité
- `fix(link)` : Correction de lien
- `refactor(nav)` : Réorganisation navigation
### Exemples
```bash
# Feature BDD
git commit -m "feat(bdd): ajouter recherche avancée dans recommendation"
# Règle métier
git commit -m "docs(rules): documenter règles de modération préventive"
# Entité
git commit -m "docs(entity): ajouter entité Campaign pour advertising"
# Fix
git commit -m "fix(link): corriger lien vers vue-ensemble.md"
```
## Ressources
- **Architecture DDD** : [domains/README.md](domains/README.md)
- **Tests BDD** : [ADR-007](adr/007-tests-bdd.md)
- **ADRs** : [adr/README.md](adr/README.md)
## Support
Questions ? Consultez :
1. Cette documentation
2. Les ADRs existants
3. Les exemples dans les domaines existants
4. L'équipe RoadWave
## Auteurs
- Équipe RoadWave
- Claude Sonnet 4.5 (documentation)
Dernière mise à jour : 2026-02-08

View File

@@ -80,6 +80,7 @@ Lorsque plusieurs contenus sont disponibles dans une zone, **seul le plus pertin
Chaque utilisateur possède des **jauges d'intérêt** qui évoluent dynamiquement :
### Catégories
- Automobile
- Voyage
- Famille
@@ -116,6 +117,7 @@ Interactions simplifiées pour sécurité routière maximale :
| **Play/Pause** | Mettre en pause / reprendre la lecture |
**Like automatique** : Le système détecte automatiquement vos préférences selon votre temps d'écoute :
- Écoute ≥80% du contenu → Like renforcé (+2 points jauge)
- Écoute 30-79% du contenu → Like standard (+1 point jauge)
- Skip après <10s → Signal négatif (-0.5 point)
@@ -198,6 +200,7 @@ Approche hybride combinant participation communautaire, IA et modérateurs dédi
| **Strike 4** | Ban définitif du compte créateur | Permanent |
**Notes** :
- **Tolérance 1ère fois** (droits d'auteur uniquement) : avertissement sans strike
- **Violations graves** (haine, illégalité, violence) : strike immédiat sans tolérance
- **Réhabilitation** : -1 strike tous les 6 mois sans nouvelle violation

View File

@@ -1,370 +0,0 @@
# Plan de refactorisation : Organisation DDD de la documentation
## 🎯 Objectif
Réorganiser 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.
## 📊 Situation actuelle
### Structure actuelle
```
docs/
├── regles-metier/ # 19 fichiers numérotés 01-19 + ANNEXE
├── diagrammes/ # Organisés par type (flux, états, séquences, entités)
│ ├── flux/
│ ├── etats/
│ ├── sequence/
│ └── entites/
├── adr/ # Architecture Decision Records
├── legal/ # Documentation légale
└── interfaces/ # Interfaces UI
```
### Problèmes identifiés
1. **Organisation séquentielle** : Numérotation 01-19 ne reflète pas les domaines métier
2. **Diagrammes dispersés** : Séparés des règles métier qu'ils illustrent
3. **Navigation complexe** : Difficile de trouver toute la doc d'un domaine
4. **Pas d'alignement code** : Structure docs ≠ structure `backend/internal/`
5. **Onboarding difficile** : Nouveau dev doit parcourir 19 fichiers linéairement
6. **Maintenance** : Règles métier, entités et diagrammes d'un même domaine sont éparpillés
## 🎨 Architecture cible (DDD)
### Nouvelle structure
```
docs/
├── domains/ # 🆕 Organisation par domaine
│ ├── README.md # Context Map + Index domaines
│ │
│ ├── _shared/ # Core Domain
│ │ ├── README.md
│ │ ├── rules/
│ │ │ ├── authentification.md
│ │ │ ├── rgpd.md
│ │ │ └── gestion-erreurs.md
│ │ ├── entities/
│ │ │ └── modele-global.md
│ │ └── ubiquitous-language.md
│ │
│ ├── recommendation/ # Bounded Context
│ │ ├── README.md
│ │ ├── rules/
│ │ │ ├── centres-interet-jauges.md
│ │ │ ├── algorithme-recommandation.md
│ │ │ └── interactions-navigation.md
│ │ ├── entities/
│ │ │ └── modele-recommandation.md
│ │ ├── sequences/
│ │ │ └── scoring-recommandation.md
│ │ └── features/
│ │ └── *.feature
│ │
│ ├── content/ # Bounded Context
│ │ ├── README.md
│ │ ├── rules/
│ │ │ ├── creation-publication.md
│ │ │ ├── audio-guides.md
│ │ │ ├── radio-live.md
│ │ │ ├── contenus-geolocalises.md
│ │ │ └── detection-contenu-protege.md
│ │ ├── entities/
│ │ │ ├── modele-audio-guides.md
│ │ │ └── modele-radio-live.md
│ │ └── flows/
│ │
│ ├── advertising/ # Bounded Context
│ │ ├── README.md
│ │ ├── rules/
│ │ │ └── publicites.md
│ │ ├── entities/
│ │ │ └── modele-publicites.md
│ │ ├── sequences/
│ │ ├── states/
│ │ └── flows/
│ │
│ ├── premium/ # Bounded Context
│ │ ├── README.md
│ │ ├── rules/
│ │ │ ├── premium.md
│ │ │ ├── mode-offline.md
│ │ │ └── abonnements-notifications.md
│ │ ├── entities/
│ │ │ └── modele-premium.md
│ │ └── sequences/
│ │
│ ├── monetization/ # Bounded Context
│ │ ├── README.md
│ │ ├── rules/
│ │ │ └── monetisation-createurs.md
│ │ ├── entities/
│ │ │ └── modele-monetisation.md
│ │ └── flows/
│ │
│ └── moderation/ # Bounded Context
│ ├── README.md
│ ├── rules/
│ │ ├── moderation-flows.md
│ │ ├── moderation-communautaire.md
│ │ └── autres-comportements.md
│ ├── entities/
│ │ └── modele-moderation.md
│ ├── sequences/
│ │ └── processus-appel-moderation.md
│ ├── states/
│ │ └── signalement-lifecycle.md
│ ├── flows/
│ │ └── moderation-signalement.md
│ └── features/
├── adr/ # Inchangé
├── legal/ # Inchangé
├── interfaces/ # Inchangé
└── technical.md # Inchangé
```
## 📋 Mapping des domaines
### 7 Bounded Contexts identifiés
| Domaine | Règles métier | Entités | Diagrammes | Responsabilité |
|---------|--------------|---------|------------|----------------|
| **_shared** | 01, 02, 10 | USERS, CONTENTS, SUBSCRIPTIONS, LISTENING_HISTORY | - | Authentification, RGPD, Gestion erreurs |
| **recommendation** | 03, 04, 05 | USER_INTERESTS, INTEREST_CATEGORIES | scoring-recommandation.md | Jauges, Algorithme, Navigation |
| **content** | 06, 07, 11, 12, 13 | AUDIO_GUIDES, LIVE_STREAMS, GUIDE_SEQUENCES, LIVE_RECORDINGS | - | Création, Audio-guides, Live, Détection droits |
| **advertising** | 16 | AD_CAMPAIGNS, AD_METRICS, AD_IMPRESSIONS | - | Campagnes, Ciblage, Métriques |
| **premium** | 08, 09, 17 | PREMIUM_SUBSCRIPTIONS, ACTIVE_STREAMS, OFFLINE_DOWNLOADS | - | Abonnements, Offline, Notifications |
| **monetization** | 18 | CREATOR_MONETIZATION, REVENUES, PAYOUTS | - | KYC, Revenus, Versements |
| **moderation** | 14, 15, 19 | REPORTS, SANCTIONS, APPEALS, STRIKES, BADGES | processus-appel-moderation.md, signalement-lifecycle.md, moderation-signalement.md | Signalements, Sanctions, Badges |
## 🗺️ Plan de migration détaillé
### Phase 1 : Créer la structure cible
```bash
# Créer l'arborescence
mkdir -p docs/domains/{_shared,recommendation,content,advertising,premium,monetization,moderation}/{rules,entities,sequences,states,flows,features}
```
### Phase 2 : Déplacer les règles métier
| Fichier actuel | Destination |
|----------------|-------------|
| `01-authentification-inscription.md` | `domains/_shared/rules/authentification.md` |
| `02-conformite-rgpd.md` | `domains/_shared/rules/rgpd.md` |
| `03-centres-interet-jauges.md` | `domains/recommendation/rules/centres-interet-jauges.md` |
| `04-algorithme-recommandation.md` | `domains/recommendation/rules/algorithme-recommandation.md` |
| `05-interactions-navigation.md` | `domains/recommendation/rules/interactions-navigation.md` |
| `06-audio-guides-multi-sequences.md` | `domains/content/rules/audio-guides.md` |
| `07-contenus-geolocalises-voiture.md` | `domains/content/rules/contenus-geolocalises.md` |
| `08-mode-offline.md` | `domains/premium/rules/mode-offline.md` |
| `09-abonnements-notifications.md` | `domains/premium/rules/abonnements-notifications.md` |
| `10-gestion-erreurs.md` | `domains/_shared/rules/gestion-erreurs.md` |
| `11-creation-publication-contenu.md` | `domains/content/rules/creation-publication.md` |
| `12-radio-live.md` | `domains/content/rules/radio-live.md` |
| `13-detection-contenu-protege.md` | `domains/content/rules/detection-contenu-protege.md` |
| `14-moderation-flows.md` | `domains/moderation/rules/moderation-flows.md` |
| `15-moderation-communautaire.md` | `domains/moderation/rules/moderation-communautaire.md` |
| `16-publicites.md` | `domains/advertising/rules/publicites.md` |
| `17-premium.md` | `domains/premium/rules/premium.md` |
| `18-monetisation-createurs.md` | `domains/monetization/rules/monetisation-createurs.md` |
| `19-autres-comportements.md` | `domains/moderation/rules/autres-comportements.md` |
| `ANNEXE-POST-MVP.md` | `domains/_shared/rules/ANNEXE-POST-MVP.md` |
### Phase 3 : Déplacer les diagrammes d'entités
| Fichier actuel | Destination |
|----------------|-------------|
| `diagrammes/entites/modele-global.md` | `domains/_shared/entities/modele-global.md` |
| `diagrammes/entites/modele-recommandation.md` | `domains/recommendation/entities/modele-recommandation.md` |
| `diagrammes/entites/modele-audio-guides.md` | `domains/content/entities/modele-audio-guides.md` |
| `diagrammes/entites/modele-radio-live.md` | `domains/content/entities/modele-radio-live.md` |
| `diagrammes/entites/modele-publicites.md` | `domains/advertising/entities/modele-publicites.md` |
| `diagrammes/entites/modele-premium.md` | `domains/premium/entities/modele-premium.md` |
| `diagrammes/entites/modele-monetisation.md` | `domains/monetization/entities/modele-monetisation.md` |
| `diagrammes/entites/modele-moderation.md` | `domains/moderation/entities/modele-moderation.md` |
### Phase 4 : Déplacer les autres diagrammes
| Fichier actuel | Destination |
|----------------|-------------|
| `diagrammes/flux/moderation-signalement.md` | `domains/moderation/flows/moderation-signalement.md` |
| `diagrammes/etats/signalement-lifecycle.md` | `domains/moderation/states/signalement-lifecycle.md` |
| `diagrammes/sequence/processus-appel-moderation.md` | `domains/moderation/sequences/processus-appel-moderation.md` |
### Phase 5 : Créer les README.md de domaine
Créer un README.md dans chaque domaine avec le template suivant :
```markdown
# Domaine : [Nom]
## Vue d'ensemble
[Description du bounded context]
## Responsabilités
- Responsabilité 1
- Responsabilité 2
## Règles métier
- [Règle 1](rules/xxx.md)
## Modèle de données
- [Diagramme entités](entities/modele-xxx.md)
## Diagrammes
- [Flux](flows/xxx.md)
- [États](states/xxx.md)
- [Séquences](sequences/xxx.md)
## Tests BDD
- [Feature 1](features/xxx.feature)
## Dépendances
- ✅ Dépend de : `_shared`
- ⚠️ Interactions avec : `moderation`
## Ubiquitous Language
**Termes métier spécifiques au domaine**
```
### Phase 6 : Déplacer les features Gherkin
```bash
# Les features actuellement dans /features/ root
mv features/api/recommendation/* domains/recommendation/features/
mv features/moderation/* domains/moderation/features/
# etc.
```
### Phase 7 : Créer le Context Map
Créer `docs/domains/README.md` avec la cartographie des domaines :
```markdown
# Context Map RoadWave
## Vue d'ensemble des domaines
[Diagramme Mermaid des relations entre bounded contexts]
## Bounded Contexts
### Core Domain
- **_shared** : Authentification, RGPD, Gestion erreurs
### Supporting Subdomains
- **recommendation** : Jauges, Algorithme, Scoring
- **content** : Création, Audio-guides, Live
- **moderation** : Signalements, Sanctions, Badges
### Generic Subdomains
- **advertising** : Campagnes publicitaires
- **premium** : Abonnements, Offline
- **monetization** : Revenus créateurs
```
### Phase 8 : Mettre à jour mkdocs.yml
Réorganiser la navigation MkDocs pour refléter la nouvelle structure par domaine.
### Phase 9 : Mettre à jour les liens internes
Corriger tous les liens relatifs dans les fichiers markdown pour pointer vers les nouvelles locations.
### Phase 10 : Nettoyer l'ancienne structure
```bash
# Une fois tout migré et testé
rm -rf docs/regles-metier/
rm -rf docs/diagrammes/
```
## ✅ Avantages attendus
1. **Cohésion forte** : Toute la doc d'un domaine au même endroit
2. **Couplage faible** : Domaines indépendants, dépendances explicites
3. **Navigabilité améliorée** : README par domaine = entrée claire
4. **Alignement code/docs** : Miroir de `backend/internal/`
5. **Onboarding facilité** : Nouveau dev explore domaine par domaine
6. **Maintenance simplifiée** : Modifier un domaine sans toucher aux autres
7. **Scalabilité** : Facile d'ajouter un nouveau domaine
8. **Tests BDD intégrés** : Features au plus près des règles métier
## ⚠️ Risques et précautions
### Risques identifiés
1. **Liens cassés** : Nombreux liens internes à corriger
2. **Confusion temporaire** : Équipe doit s'adapter à la nouvelle structure
3. **MkDocs rebuild** : Navigation complète à refaire
4. **Features Gherkin** : Potentiellement beaucoup de fichiers à déplacer
### Précautions
1.**Créer ce plan d'abord** : Validation avant exécution
2.**Branch dédiée** : `refactor/ddd-documentation`
3.**Commits atomiques** : Un commit par phase
4.**Tests continus** : Vérifier MkDocs build après chaque phase
5.**Backup** : Garder ancienne structure jusqu'à validation complète
6.**Script automatisé** : Créer script pour les déplacements et corrections de liens
## 📝 Checklist d'exécution
- [ ] Valider ce plan avec l'équipe
- [ ] Créer branch `refactor/ddd-documentation`
- [ ] Phase 1 : Créer arborescence
- [ ] Phase 2 : Déplacer règles métier
- [ ] Phase 3 : Déplacer diagrammes entités
- [ ] Phase 4 : Déplacer autres diagrammes
- [ ] Phase 5 : Créer README.md domaines
- [ ] Phase 6 : Déplacer features Gherkin
- [ ] Phase 7 : Créer Context Map
- [ ] Phase 8 : Mettre à jour mkdocs.yml
- [ ] Phase 9 : Corriger liens internes
- [ ] Phase 10 : Nettoyer ancienne structure
- [ ] Tester build MkDocs
- [ ] Valider avec équipe
- [ ] Merger dans main
## 🚀 Script d'automatisation suggéré
```bash
#!/bin/bash
# scripts/refactor-ddd.sh
# Phase 1 : Créer structure
echo "Phase 1: Création structure..."
mkdir -p docs/domains/{_shared,recommendation,content,advertising,premium,monetization,moderation}/{rules,entities,sequences,states,flows,features}
# Phase 2 : Déplacer règles métier
echo "Phase 2: Migration règles métier..."
git mv docs/regles-metier/01-authentification-inscription.md docs/domains/_shared/rules/authentification.md
# ... etc pour tous les fichiers
# Phase 3-4 : Déplacer diagrammes
echo "Phase 3-4: Migration diagrammes..."
git mv docs/diagrammes/entites/modele-global.md docs/domains/_shared/entities/modele-global.md
# ... etc
# Phase 9 : Corriger liens (sed ou script Python)
echo "Phase 9: Correction liens..."
find docs/domains -name "*.md" -exec sed -i 's|../../regles-metier/|../rules/|g' {} \;
# ... etc
echo "Migration terminée!"
```
## 📚 Références DDD
- [Domain-Driven Design - Eric Evans](https://www.domainlanguage.com/ddd/)
- [Bounded Context - Martin Fowler](https://martinfowler.com/bliki/BoundedContext.html)
- [Context Mapping](https://github.com/ddd-crew/context-mapping)
---
**Date de création** : 2026-02-07
**Statut** : 🟡 En attente de validation
**Auteur** : Documentation refactoring initiative

View File

@@ -8,6 +8,7 @@
|-----------|-------------|-----|
| **Backend** | Go + Fiber | [ADR-001](adr/001-langage-backend.md) |
| **Architecture Backend** | Monolithe Modulaire | [ADR-010](adr/010-architecture-backend.md) |
| **Librairies Backend** | Fiber, pgx, rueidis, sqlc, etc. | [ADR-018](adr/018-librairies-go.md) |
| **Authentification** | Zitadel (self-hosted OVH) | [ADR-008](adr/008-authentification.md) |
| **Streaming** | HLS | [ADR-002](adr/002-protocole-streaming.md) |
| **Codec** | Opus | [ADR-003](adr/003-codec-audio.md) |
@@ -30,8 +31,7 @@
| **Modération** | Architecture modération | [ADR-023](adr/023-architecture-moderation.md) |
| **Monitoring** | Prometheus + Grafana | [ADR-024](adr/024-monitoring-observabilite.md) |
| **Secrets** | Vault + sealed secrets | [ADR-025](adr/025-securite-secrets.md) |
| **Notifications géo** | Push + geofencing | [ADR-017](adr/017-notifications-geolocalisees.md) |
| **Notifications push** | FCM + APNS | [ADR-018](adr/017-notifications-geolocalisees.md) |
| **Notifications** | Push géolocalisées (FCM/APNS + geofencing) | [ADR-017](adr/017-notifications-geolocalisees.md) |
---
@@ -113,77 +113,72 @@ TTL cache : 5 minutes (le contenu ne bouge pas).
## Architecture Services
```mermaid
flowchart LR
subgraph clients["📱 Clients"]
direction TB
flowchart TB
subgraph clients["Clients"]
mobile["Mobile Apps<br/>iOS/Android<br/>Flutter"]
carplay["CarPlay /<br/>Android Auto"]
end
subgraph ovh["🇫🇷 OVH VPS Essential (Gravelines, France)"]
direction TB
subgraph ovh["OVH VPS Essential (Gravelines, France)"]
nginx["NGINX Cache<br/>+ Let's Encrypt<br/>TLS 1.3, Rate Limiting"]
api["API Gateway<br/>Go + Fiber :8080"]
nginx["🌐 NGINX<br/>Cache + TLS 1.3<br/>Rate Limiting"]
api["🚪 API Gateway<br/>Go + Fiber<br/>:8080"]
subgraph services["Backend (Monolithe Modulaire)"]
direction LR
auth["🔐 Auth"]
user["👤 User"]
content["🎙️ Content/Geo"]
streaming["📡 Streaming"]
payment["💳 Payment"]
notif["🔔 Notif"]
subgraph services["Backend Services (Monolithe Modulaire)"]
auth["Auth Service<br/>JWT validation"]
user["User Service<br/>Profils, Jauges"]
content["Content/Geo Service<br/>Recommandations<br/>PostGIS queries"]
streaming["Streaming Service<br/>HLS generation"]
payment["Payment Service<br/>Mangopay integration"]
notif["Notification Service<br/>FCM/APNS"]
end
zitadel["🔑 Zitadel<br/>OAuth2 PKCE<br/>:8081"]
ip2loc["🌍 IP2Location<br/>SQLite 50MB"]
zitadel["Zitadel IdP<br/>OAuth2 PKCE<br/>:8081"]
ip2loc["IP2Location DB<br/>SQLite ~50MB<br/>Mode dégradé"]
subgraph data["💾 Données"]
direction TB
pgbouncer["PgBouncer<br/>:6432"]
postgres["PostgreSQL 16<br/>+ PostGIS 3.4"]
redis["Redis 7<br/>Cache + Geo"]
subgraph data["Données"]
pgbouncer["PgBouncer<br/>Connection pooling<br/>:6432"]
postgres["PostgreSQL 16<br/>+ PostGIS 3.4<br/>Schémas:<br/>- roadwave<br/>- zitadel"]
redis["Redis 7 Cluster<br/>Cache + Geospatial<br/>GEORADIUS"]
end
end
subgraph external["☁️ Services Externes"]
direction TB
storage["OVH Object Storage<br/>(Fichiers HLS)"]
mangopay["Mangopay<br/>(Paiements/KYC)"]
brevo["Brevo<br/>(Emails)"]
fcm["FCM/APNS<br/>(Push)"]
subgraph external["Services Externes"]
storage["OVH Object Storage<br/>Fichiers audio HLS"]
mangopay["Mangopay<br/>Paiements, KYC"]
brevo["Brevo<br/>Emails transactionnels"]
fcm["FCM / APNS<br/>Push notifications"]
end
%% Flux clients
clients --> nginx
mobile --> nginx
carplay --> nginx
nginx --> api
%% API vers services
api --> services
api --> auth
api --> user
api --> content
api --> streaming
api --> payment
api --> notif
api --> ip2loc
%% Services vers infra
auth --> zitadel
user --> data
content --> data
user --> pgbouncer
user --> redis
content --> pgbouncer
content --> redis
streaming --> storage
payment --> mangopay
notif --> fcm
%% Infra interne
zitadel --> pgbouncer
pgbouncer --> postgres
%% Retours vers clients
brevo -.email.-> clients
fcm -.push.-> clients
brevo -.email.-> mobile
fcm -.push.-> mobile
style ovh fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style external fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style clients fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style data fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style services fill:#fff,stroke:#666,stroke-width:1px
style ovh fill:#e3f2fd
style external fill:#fff3e0
style clients fill:#f3e5f5
style data fill:#e8f5e9
```
**Souveraineté** : 100% données en France (RGPD compliant)

View File

@@ -47,6 +47,7 @@ Streaming audio vers des utilisateurs mobiles en voiture, avec réseaux instable
La latence HLS (5-30s) entre en conflit avec les notifications géolocalisées qui doivent déclencher l'audio **au moment précis** où l'utilisateur atteint un point d'intérêt.
**Exemple critique** :
- Utilisateur en voiture à 90 km/h (25 m/s)
- ETA de 7 secondes avant le point → notification affichée
- Latence HLS de 15 secondes
@@ -69,6 +70,7 @@ La latence HLS (5-30s) entre en conflit avec les notifications géolocalisées q
```
**Stratégie de cache** :
- Télécharge les **15 premières secondes** de chaque POI à proximité
- Limite : 3 POI simultanés en cache (max ~25 MB)
- Purge automatique après 200m de distance passée
@@ -152,16 +154,19 @@ Si le pre-buffer échoue (réseau faible, pas de cache), afficher un **loader av
### Impact sur l'Infrastructure
#### Backend (Go)
- **Nouveau service** : `audiocache.Service` pour préparer les extraits M4A
- **Endpoint** : `GET /api/v1/audio/poi/:id/intro` (retourne 15s d'audio)
- **CDN** : Cache NGINX avec TTL 7 jours sur `/audio/*/intro.m4a`
#### Mobile (Flutter)
- **Package** : `just_audio` avec cache local (`flutter_cache_manager`)
- **Stockage** : Max 100 MB de cache audio (auto-purge LRU)
- **Logique** : `PreBufferService` avec scoring de priorité POI
#### Coûts
- **Bande passante** : +10-15 MB/utilisateur/session (vs streaming pur)
- **Stockage CDN** : +500 MB pour 1000 POI × 5 MB intro (négligeable)
- **Économie** : Cache CDN réduit les requêtes origin (-60% selon tests)

View File

@@ -44,12 +44,14 @@ flowchart LR
## Justification
### PostgreSQL + PostGIS
- Requêtes géospatiales complexes et précises
- Index GIST pour performance
- ACID, fiabilité éprouvée
- Écosystème mature
### PgBouncer
- **Connection pooling** : Réduit l'overhead de création de connexions PostgreSQL
- **Mode transaction** : Connexion réutilisée entre transactions (optimal pour API stateless)
- **Performance** : Permet de gérer 1000+ connexions concurrentes avec ~100 connexions réelles à PostgreSQL
@@ -57,6 +59,7 @@ flowchart LR
- **Port** : :6432 (vs :5432 pour PostgreSQL direct)
### Redis
- Cache géo natif (`GEORADIUS`) : 100K+ requêtes/sec
- Sessions utilisateurs
- Pub/sub pour temps réel
@@ -81,11 +84,13 @@ LIMIT 20;
### Configuration PgBouncer
**Mode recommandé** : `transaction`
- Connexion libérée après chaque transaction
- Optimal pour API stateless (Go + Fiber)
- Maximise la réutilisation des connexions
**Pool sizing** :
- `default_pool_size` : 20 (connexions par base)
- `max_client_conn` : 1000 (connexions clients max)
- `reserve_pool_size` : 5 (connexions de secours)
@@ -115,8 +120,3 @@ server_idle_timeout = 600
// Après (via PgBouncer)
dsn := "postgres://user:pass@localhost:6432/roadwave"
```
## Documentation technique détaillée
- [Diagramme de séquence cache géospatial](../architecture/sequences/cache-geospatial.md)
-

View File

@@ -67,11 +67,13 @@ features/
**Exécuté par** : Backend (Godog + step definitions Go dans `/backend/tests/bdd/`)
**Exemples de scénarios** :
- `POST /api/v1/auth/register` avec email invalide → retourne 400
- `GET /api/v1/contents/nearby` avec rayon 500m → retourne POI triés par distance
- `DELETE /api/v1/user/account` → supprime données RGPD conformément
**Caractéristiques** :
- Focus sur la **logique métier backend** (algorithme, validation, persistance)
- Pas d'interface utilisateur
- Testable via requêtes HTTP directes
@@ -85,11 +87,13 @@ features/
**Exécuté par** : Mobile (Flutter `integration_test` + step definitions Dart dans `/mobile/tests/bdd/`)
**Exemples de scénarios** :
- Cliquer sur "Lecture" → widget audio player s'affiche avec progress bar
- Mode piéton activé → carte interactive affichée + bouton "Télécharger zone"
- Scroll dans liste podcasts → lazy loading déclenche pagination
**Caractéristiques** :
- Focus sur l'**expérience utilisateur mobile**
- Validation visuelle (widgets, animations, navigation)
- Mock du backend si nécessaire (tests UI isolés)
@@ -103,11 +107,13 @@ features/
**Exécuté par** : Backend **ET** Mobile ensemble
**Exemples de scénarios** :
- Abonnement Premium : Formulaire mobile → API Zitadel → API RoadWave → Mangopay → Confirmation UI
- Erreur réseau : Perte connexion pendant streaming → Fallback mode offline → Reprise auto après reconnexion
- Notification géolocalisée : GPS détecte POI → Backend calcule recommandation → Push notification → Ouverture app → Lecture audio
**Caractéristiques** :
- Tests **cross-composants** (intégration complète)
- Implique souvent des **services tiers** (Zitadel, Mangopay, Firebase)
- Validation du **parcours utilisateur de bout en bout**
@@ -117,10 +123,12 @@ features/
### Implémentation Step Definitions
**Backend** : `/backend/tests/bdd/`
- Step definitions Go pour features `api/` et `e2e/`
- Utilise `godog` et packages backend (`service`, `repository`)
**Mobile** : `/mobile/tests/bdd/`
- Step definitions Dart pour features `ui/` et `e2e/`
- Utilise Flutter `integration_test` framework

View File

@@ -14,11 +14,13 @@ RoadWave nécessite un système d'authentification sécurisé pour mobile (iOS/A
**Zitadel self-hosted sur OVH France** pour l'IAM avec validation JWT locale côté API Go.
**Méthode d'authentification** : **Email/Password uniquement** (pas d'OAuth tiers)
- ✅ Authentification native Zitadel (email + mot de passe)
-**Pas de fournisseurs OAuth externes** (Google, Apple, Facebook)
- **Protocole** : OAuth2 PKCE (entre app mobile et Zitadel uniquement)
**Architecture de déploiement** :
- Container Docker sur le même VPS OVH (Gravelines, France) que l'API
- Base de données PostgreSQL partagée avec RoadWave (séparation logique par schéma)
- Aucune donnée d'authentification ne transite par des serveurs tiers
@@ -102,12 +104,14 @@ graph TB
4. App mobile → Go API avec JWT → validation locale
**Ce que nous N'UTILISONS PAS** :
- ❌ "Sign in with Google"
- ❌ "Sign in with Apple"
- ❌ "Sign in with Facebook"
- ❌ Aucun autre fournisseur externe
**Pourquoi OAuth2 alors ?** :
- OAuth2 PKCE est le **standard moderne** pour auth mobile (sécurisé, refresh tokens, etc.)
- Zitadel implémente OAuth2/OIDC comme **protocole**, mais l'auth reste email/password
- Alternative serait session cookies (moins adapté mobile) ou JWT custom (réinventer la roue)

View File

@@ -45,6 +45,7 @@ RoadWave nécessite applications iOS et Android avec support CarPlay/Android Aut
- **Cache images** : `cached_network_image` (LRU cache)
**Points d'attention** :
- ⚠️ **Permissions progressives requises** pour `geofence_service` et `geolocator` (voir section "Stratégie de Permissions")
- ⚠️ **Licences** : 100% permissives (MIT, BSD-3) - voir ADR-020
@@ -53,6 +54,7 @@ RoadWave nécessite applications iOS et Android avec support CarPlay/Android Aut
### Contexte et Enjeux
**Problème** : La géolocalisation en arrière-plan (requise pour le mode piéton) est **très scrutée** par Apple et Google :
- **iOS App Store** : Taux de rejet ~70% si permission "Always Location" mal justifiée
- **Android Play Store** : `ACCESS_BACKGROUND_LOCATION` nécessite déclaration spéciale depuis Android 10
- **RGPD** : Permissions doivent être **optionnelles** et l'app **utilisable sans**
@@ -74,6 +76,7 @@ Trois niveaux de permissions doivent être gérés :
**Quand** : Premier lancement de l'app
**Demande** : `locationWhenInUse` uniquement
- iOS : "Allow While Using App"
- Android : `ACCESS_FINE_LOCATION`
@@ -136,21 +139,25 @@ Pour vous proposer du contenu audio adapté
Le service de gestion des permissions (`lib/core/services/location_permission_service.dart`) doit implémenter :
**Détection du niveau actuel** :
- Vérifier le statut de la permission `location` (when in use)
- Vérifier le statut de la permission `locationAlways` (background)
- Retourner le niveau le plus élevé accordé
**Demande de permission de base** (Étape 1) :
- Demander uniquement la permission `location` (when in use)
- Utilisée lors de l'onboarding
- Aucun écran d'éducation requis
**Demande de permission arrière-plan** (Étape 2) :
- **Toujours** afficher un écran d'éducation AVANT la demande OS
- Demander la permission `locationAlways` (iOS) ou `ACCESS_BACKGROUND_LOCATION` (Android)
- Si refusée de manière permanente, proposer l'ouverture des réglages système
**Gestion des refus** :
- Détecter si la permission est refusée de manière permanente
- Proposer l'ouverture des réglages de l'appareil avec un message clair
- Permettre à l'utilisateur d'annuler
@@ -160,6 +167,7 @@ Le service de gestion des permissions (`lib/core/services/location_permission_se
#### iOS (`ios/Runner/Info.plist`)
**Clés requises** :
- `NSLocationWhenInUseUsageDescription` : Décrire l'usage pour le mode voiture (contenu géolocalisé en temps réel)
- `NSLocationAlwaysAndWhenInUseUsageDescription` : Décrire l'usage optionnel pour le mode piéton (notifications audio-guides en arrière-plan), mentionner explicitement que c'est optionnel et désactivable
- `UIBackgroundModes` : Activer les modes `location` et `remote-notification`
@@ -170,11 +178,13 @@ Le service de gestion des permissions (`lib/core/services/location_permission_se
#### Android (`android/app/src/main/AndroidManifest.xml`)
**Permissions requises** :
- `ACCESS_FINE_LOCATION` et `ACCESS_COARSE_LOCATION` : Permission de base (when in use)
- `ACCESS_BACKGROUND_LOCATION` : Permission arrière-plan (Android 10+), nécessite justification Play Store
- `FOREGROUND_SERVICE` et `FOREGROUND_SERVICE_LOCATION` : Service persistant pour mode piéton (Android 12+)
**Android Play Store** : Déclaration requise dans Play Console lors de la soumission :
- Justification : "Notifications géolocalisées pour audio-guides touristiques en arrière-plan"
- Vidéo démo obligatoire montrant le flow de demande de permission
@@ -191,12 +201,14 @@ Le service de gestion des permissions (`lib/core/services/location_permission_se
### Tests de Validation Stores
**Checklist App Store (iOS)** :
- [ ] Permission "Always" demandée **uniquement** si user active mode piéton
- [ ] Écran d'éducation **avant** demande OS (requis iOS 13+)
- [ ] App fonctionne sans permission "Always" (validation critique)
- [ ] Texte `Info.plist` clair et honnête (pas de tracking publicitaire)
**Checklist Play Store (Android)** :
- [ ] Déclaration `ACCESS_BACKGROUND_LOCATION` avec justification détaillée
- [ ] Vidéo démo flow de permissions (< 30s, requis Play Console)
- [ ] App fonctionne sans permission background (validation critique)

View File

@@ -79,6 +79,7 @@ Approche **multi-niveaux** : unitaires, intégration, BDD (Gherkin), E2E, load t
- `github.com/cucumber/godog`
- `github.com/testcontainers/testcontainers-go`
- `grafana/k6` (AGPL-3.0, usage interne OK)
- Temps CI : ~3-5 min (tests unitaires + BDD)
- Tests intégration/E2E : nightly builds (15-30 min)
- Load tests : avant chaque release majeure

View File

@@ -68,6 +68,7 @@ mobile/tests/bdd/inscription_steps.dart → Teste l'UI mobile
```
Cela garantit que :
- Les spécifications métier sont uniques et cohérentes
- Chaque couche teste sa responsabilité
- Les tests valident le contrat entre front et back

View File

@@ -52,6 +52,7 @@ OVH Object Storage (~1.20€/100GB)
| **Scale** | 100K+ | Scaleway Kubernetes (Kapsule) | ~500€ | Auto-scaling OU multi-région |
**Triggers détaillés** :
- **Phase 2** : CPU > 70% (7j), latence p99 > 100ms, backups > 1h/semaine, MRR > 2000€
- **Phase 3** : Auto-scaling horizontal requis, multi-région Europe, DevOps dédié, > 5 services

View File

@@ -8,6 +8,7 @@
RoadWave nécessite un service d'envoi d'emails **techniques uniquement** (pas de notifications sociales, alertes marketing, promotions).
**Périmètre strict** :
-**Authentification** : Vérification email (inscription), réinitialisation mot de passe, changement email
-**Sécurité** : Alertes connexion inhabituelle, changement password
-**Modération** : Strikes, suspensions, bannissements

View File

@@ -45,6 +45,7 @@ Architecture hybride en **2 phases** :
5. Utilisateur clique → app s'ouvre → HLS démarre l'audio (ADR-002)
**Limitations MVP** :
- Fonctionne uniquement si l'utilisateur a envoyé sa position < 5 minutes
- En voiture rapide (>80 km/h), possible de "manquer" un POI si position pas mise à jour
@@ -96,10 +97,12 @@ Architecture hybride en **2 phases** :
### Pourquoi implémentation directe APNS/FCM et pas SDK Firebase ?
**Réalité technique** : Notifications natives requièrent obligatoirement Google/Apple
- **APNS (Apple)** : Seul protocole pour notifications iOS → dépendance Apple inévitable
- **FCM (Google)** : Protocole standard Android (Google Play Services)
**Implémentation directe choisie** :
- **Gratuit** : APNS et FCM sont gratuits (pas de limite de volume)
- **Self-hosted** : Code backend 100% maîtrisé, pas de dépendance SDK tiers
- **Fiabilité** : Infrastructure Apple/Google avec 99.95% uptime
@@ -123,6 +126,7 @@ Architecture hybride en **2 phases** :
- ❌ Toujours wrapper autour APNS/FCM
**Décision technique** :
- Implémentation directe APNS/FCM dès le MVP
- **Cohérence ADR** : Respecte ADR-008 (self-hosted) et ADR-015 (souveraineté française)
- **Abstraction layer** : Interface `NotificationProvider` pour faciliter maintenance
@@ -150,9 +154,11 @@ Architecture hybride en **2 phases** :
- ⚠️ **Gestion certificats APNS** : Renouvellement annuel + configuration
- Mitigé par scripts automation (certificats auto-renouvelés)
- Documentation complète du processus
- ⚠️ **Tokens push sensibles** : Tokens FCM/APNS stockés côté backend
- Chiffrement tokens en base (conformité RGPD)
- Rotation automatique des tokens expirés
- ❌ WebSocket nécessite maintien de connexion (charge serveur +10-20%)
- ❌ Mode offline non disponible au MVP (déception possible des early adopters)

View File

@@ -6,6 +6,7 @@
## Contexte
Le backend Go de RoadWave nécessite des librairies tierces pour HTTP, base de données, tests, streaming, etc. Le choix doit privilégier :
- **Licences permissives** (MIT, Apache-2.0, BSD) sans restrictions commerciales
- **Performance** (10M utilisateurs, 100K RPS, p99 < 100ms)
- **Maturité** et maintenance active
@@ -55,6 +56,7 @@ Utilisation de **16 librairies open-source** avec licences permissives.
## Alternatives considérées
Voir pour comparatifs complets :
- Framework : Fiber vs Gin vs Echo vs Chi
- PostgreSQL : pgx vs GORM vs database/sql
- Redis : rueidis vs go-redis vs redigo
@@ -64,17 +66,20 @@ Voir pour comparatifs complets :
## Justification
### Licences
- **15/16 librairies** : MIT, Apache-2.0, BSD, ISC (permissives)
- **1/16** : AGPL-3.0 (k6 load testing, OK usage interne)
- **Compatibilité totale** : Aucun conflit de licence
### Performance
- **Fiber** : 36K RPS (5% plus rapide que Gin/Echo)
- **pgx** : 30-50% plus rapide que GORM
- **rueidis** : Client-side caching automatique
- **zerolog** : Zero allocation, benchmarks 2025
### Maturité
- **Standards** : testify (27% adoption), golang-migrate, viper
- **Production** : Fiber (33K stars), pgx (10K stars), pion (13K stars)
- **Maintenance** : Toutes actives (commits 2025-2026)
@@ -82,12 +87,14 @@ Voir pour comparatifs complets :
## Conséquences
### Positives
- ✅ Aucune restriction licence commerciale
- ✅ Stack cohérent avec ADR existants (001, 002, 007, 008, 013, 015, 019)
- ✅ Performance validée (benchmarks publics)
- ✅ Écosystème mature et documenté
### Négatives
- ⚠️ **k6 (AGPL-3.0)** : Copyleft, mais OK pour tests internes (pas de SaaS k6 prévu)
- ⚠️ **Gestion certificats APNS** : Renouvellement annuel, configuration manuelle
- ❌ Courbe d'apprentissage : 16 librairies à maîtriser (doc nécessaire)

View File

@@ -8,10 +8,12 @@
RoadWave nécessite un service de géolocalisation par IP pour le mode dégradé (utilisateurs sans GPS activé). Ce service permet de détecter la ville/région de l'utilisateur à partir de son adresse IP et d'afficher du contenu régional même sans permission GPS.
**Évolution du marché** :
- **Avant 2019** : MaxMind GeoLite2 était téléchargeable gratuitement (base de données locale)
- **Depuis 2019** : MaxMind nécessite un compte + limite 1000 requêtes/jour (gratuit), puis 0.003$/requête au-delà
**Usage RoadWave** :
- Mode dégradé : ~10% des utilisateurs (estimation)
- Volume : 1000 utilisateurs × 10% = 100 requêtes/jour (MVP)
- Critère : Aucune dépendance à un service tiers payant
@@ -33,6 +35,7 @@ RoadWave nécessite un service de géolocalisation par IP pour le mode dégradé
### IP2Location Lite (choix retenu)
**Avantages** :
- Gratuit (pas de limite de requêtes)
- Self-hosted (souveraineté des données, cohérence avec [ADR-004](004-cdn.md))
- Base de données SQLite légère (50-100 MB)
@@ -41,12 +44,14 @@ RoadWave nécessite un service de géolocalisation par IP pour le mode dégradé
- Pas de compte tiers requis
**Inconvénients** :
- Maintenance mensuelle (mise à jour DB)
- Précision équivalente à MaxMind (~±50 km)
### MaxMind GeoLite2 API (rejeté)
**Pourquoi rejeté** :
- Coût potentiel en cas de dépassement quota (risque faible mais existant)
- Dépendance à un service tiers (perte de souveraineté)
- Compte requis (friction opérationnelle)
@@ -54,6 +59,7 @@ RoadWave nécessite un service de géolocalisation par IP pour le mode dégradé
### Self-hosted MaxMind (rejeté)
**Pourquoi rejeté** :
- Compte MaxMind obligatoire pour télécharger la DB (friction)
- Complexité identique à IP2Location pour résultat équivalent
- IP2Location offre même fonctionnalité sans compte tiers
@@ -84,28 +90,33 @@ flowchart TD
### Maintenance
**Mise à jour mensuelle** :
- Cron job télécharge nouvelle DB IP2Location (1er du mois)
- Backup DB actuelle avant remplacement
- Rechargement service GeoIP (hot reload sans downtime)
**Monitoring** :
- Alertes si DB > 60 jours (DB obsolète)
- Logs requêtes "IP non trouvée" (détection problèmes DB)
## Conséquences
### Positives
- Aucun coût récurrent (gratuit à l'infini)
- Souveraineté complète des données (cohérence ADR-004)
- Pas de dépendance externe (service tiers)
- Latence minimale (lookup local SQLite < 1ms)
### Négatives
- Maintenance mensuelle requise (automatisable)
- Précision limitée (±50 km, acceptable pour mode dégradé)
- Taille base de données (~50-100 MB sur disque)
### Risques atténués
- **DB obsolète** : Alertes automatiques si > 60 jours
- **IP non trouvée** : Fallback "France" par défaut (code pays FR)
- **Perte DB** : Backup automatique avant chaque mise à jour

View File

@@ -6,6 +6,7 @@
## Contexte
L'application mobile RoadWave (iOS/Android) nécessite des librairies tierces pour audio HLS, géolocalisation, notifications, state management, etc. Le choix doit privilégier :
- **Licences permissives** (MIT, Apache-2.0, BSD) sans restrictions commerciales
- **Maturité** et maintenance active (écosystème Flutter)
- **Performance native** (pas de bridge JS)
@@ -53,28 +54,33 @@ Utilisation de **9 librairies open-source** Flutter avec licences permissives, d
## Alternatives considérées
### State Management
- **flutter_bloc** (choisi) : Pattern BLoC, testable, reactive
- **riverpod** : Plus moderne, moins mature
- **provider** : Simple mais limité pour app complexe
- **getx** : Performance mais opinions controversées
### Audio
- **just_audio** (choisi) : HLS natif, communauté active
- **audioplayers** : Moins mature pour streaming
- **flutter_sound** : Orienté recording, pas streaming
### Géolocalisation
- **geolocator** (choisi) : Standard Flutter, 1.2K+ stars
- **location** : Moins maintenu
- **background_location** : Spécifique background uniquement
### Notifications Push
- **flutter_apns + flutter_fcm** (choisi) : Implémentation directe APNS/FCM, pas de vendor lock-in
- **firebase_messaging** : SDK Firebase, vendor lock-in Google
- **OneSignal** : Plus cher (500€/mois @ 100K users), vendor lock-in
- **Custom WebSocket** : Complexe, toujours besoin APNS/FCM au final (voir ADR-017)
### Geofencing (Phase 2)
- **geofence_service** (choisi) : Natif iOS/Android, économie batterie optimale
- **background_geolocation** : Payant (149$/an par app)
- **flutter_background_location** : Moins mature
@@ -82,17 +88,20 @@ Utilisation de **9 librairies open-source** Flutter avec licences permissives, d
## Justification
### Licences
- **7/9 librairies** : MIT (permissive totale)
- **2/9** : BSD-3 (permissive, compatible commercial)
- **Compatibilité totale** : Aucun conflit de licence, aucune restriction commerciale
### Maturité
- **flutter_bloc** : 11.6K stars, adoption large (state management standard)
- **just_audio** : 900+ stars, utilisé production (podcasts apps)
- **geolocator** : 1.2K stars, maintenu BaseFlow (entreprise Flutter)
- **dio** : 12K+ stars, client HTTP le plus utilisé Flutter
### Performance
- **Compilation native** : Dart → ARM64 (pas de bridge JS comme React Native)
- **just_audio** : Utilise AVPlayer (iOS) et ExoPlayer (Android) natifs
- **geolocator** : Accès direct CoreLocation (iOS) et FusedLocation (Android)
@@ -100,6 +109,7 @@ Utilisation de **9 librairies open-source** Flutter avec licences permissives, d
- **geofence_service** (Phase 2) : Geofencing natif, minimise consommation batterie
### Conformité Stores
- **Permissions progressives** : `permission_handler` + stratégie ADR-010
- **Background modes MVP** : `geolocator` (When In Use) + `firebase_messaging` approuvés stores
- **Background modes Phase 2** : `geofence_service` nécessite permission "Always" (taux acceptation ~30%)
@@ -171,6 +181,7 @@ graph TB
## Conséquences
### Positives
- ✅ Aucune restriction licence commerciale (100% permissif)
- ✅ Stack cohérent avec ADR-010 (Frontend Mobile)
- ✅ Performance native (compilation ARM64 directe)
@@ -179,6 +190,7 @@ graph TB
- ✅ Conformité stores (permissions progressives)
### Négatives
- ⚠️ **CarPlay/Android Auto** : Packages communautaires (pas officiels Flutter)
- ⚠️ **Configuration APNS/FCM** : Gestion certificats et OAuth2, configuration manuelle
- ⚠️ **Permission "Always" Phase 2** : Taux acceptation ~30% (geofencing local)
@@ -190,6 +202,7 @@ graph TB
> **Note** : Les versions exactes seront définies lors de l'implémentation. Cette section indique les packages requis, non les versions à utiliser (qui évoluent rapidement dans l'écosystème Flutter).
**Core (Phase 1 MVP)** :
- `flutter_bloc` - State management
- `just_audio` - Audio HLS streaming
- `dio` - HTTP client
@@ -197,6 +210,7 @@ graph TB
- `cached_network_image` - Cache images
**Géolocalisation & Notifications (Phase 1 MVP)** :
- `geolocator` - GPS haute précision, WebSocket position updates
- `flutter_apns` - Push notifications APNS natif iOS (ADR-017)
- `flutter_fcm` - Push notifications FCM natif Android (ADR-017)
@@ -204,10 +218,12 @@ graph TB
- `permission_handler` - Gestion permissions
**CarPlay/Android Auto (optionnels Phase 1)** :
- `flutter_carplay` - Intégration CarPlay
- `android_auto_flutter` - Support Android Auto
**Geofencing (Phase 2 Post-MVP)** :
- `geofence_service` - Geofencing local pour mode offline (ADR-017 Phase 2)
### Migration depuis ADR-010
@@ -219,15 +235,18 @@ La section "Packages clés" de l'ADR-010 est désormais obsolète et doit réfé
## Risques et Mitigations
### Risque 1 : CarPlay/Android Auto packages communautaires
- **Impact** : Maintenance non garantie par Flutter team
- **Mitigation** : Fork privé si besoin, contribution upstream, ou développement custom si critique
### Risque 2 : Validation App Store (permissions background)
- **Impact** : Taux de rejet ~70% si mal justifié
- **Mitigation Phase 1** : Permission "When In Use" seulement (MVP), moins scrutée par Apple
- **Mitigation Phase 2** : Stratégie progressive (ADR-010), écrans d'éducation, tests beta TestFlight pour permission "Always"
### Risque 3 : Performance audio HLS en arrière-plan
- **Impact** : Interruptions si OS tue l'app
- **Mitigation** : Background audio task iOS, foreground service Android (natif dans `just_audio`)

View File

@@ -14,6 +14,7 @@ L'application nécessite un système de cache performant pour plusieurs cas d'us
- **Rate limiting** : Protection contre les abus API
Les contraintes de performance sont strictes :
- Latence p99 < 5ms pour les requêtes de cache
- Support de 100K+ requêtes/seconde en lecture
- Persistance optionnelle (données non critiques)
@@ -24,6 +25,7 @@ Les contraintes de performance sont strictes :
**Redis 7+ en mode Cluster** sera utilisé comme solution de cache principale.
Configuration :
- Mode Cluster avec 3 nœuds minimum (haute disponibilité)
- Persistence RDB désactivée pour les caches chauds (performance maximale)
- AOF activé uniquement pour les sessions utilisateurs (durabilité)
@@ -76,6 +78,7 @@ Ces commandes permettent de servir les requêtes de proximité directement depui
### Écosystème Go
Librairie `go-redis/redis` (13K+ stars GitHub) :
- Support complet Redis Cluster
- Pipeline et transactions
- Context-aware (intégration Go idiomatique)
@@ -84,6 +87,7 @@ Librairie `go-redis/redis` (13K+ stars GitHub) :
### Pub/Sub pour temps réel
Support natif de messaging publish/subscribe pour :
- Notifications push (invalidation de cache)
- Événements temps réel (nouveau contenu géolocalisé)
- Coordination entre instances API (scaling horizontal)
@@ -106,12 +110,14 @@ Support natif de messaging publish/subscribe pour :
### Stratégie de cache
**TTL par type de donnée** :
- Métadonnées de contenu : 15 minutes (mise à jour rare)
- Résultats géolocalisés : 5 minutes (contenus statiques géographiquement)
- Sessions utilisateurs : 24 heures (renouvellement automatique)
- Rate limiting : 1 minute (fenêtre glissante)
**Invalidation** :
- Publication de contenu → `DEL` métadonnées + publication Pub/Sub
- Modification géolocalisation → `GEOREM` puis `GEOADD`
- Logout utilisateur → `DEL` session
@@ -119,21 +125,25 @@ Support natif de messaging publish/subscribe pour :
### Configuration production
**Cluster 3 nœuds** (minimum haute disponibilité) :
- 1 master + 2 replicas
- Répartition sur 3 zones de disponibilité (anti-affinité)
- `cluster-require-full-coverage no` → lecture dégradée si nœud down
**Mémoire** :
- `maxmemory 2gb` par nœud (ajustable selon charge)
- `maxmemory-policy allkeys-lru` → éviction automatique anciennes clés
**Persistance** :
- RDB désactivé (`save ""`) pour caches chauds
- AOF `appendonly yes` uniquement pour sessions (nœud dédié optionnel)
### Monitoring
Métriques critiques à suivre :
- Taux de hit/miss par namespace (target >95% hit rate)
- Latence p99 par commande (alerter si >10ms)
- Fragmentation mémoire (rebalance si >1.5)

View File

@@ -8,6 +8,7 @@
RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documentation et features BDD ([ADR-014](014-organisation-monorepo.md)). Sans optimisation, chaque commit déclencherait **tous** les builds (backend + mobile + docs), même si seul un composant a changé.
**Problématique** :
- ❌ Temps de CI/CD inutilement longs (rebuild complet ~15 min)
- ❌ Gaspillage de ressources GitHub Actions
- ❌ Ralentissement du feedback développeur
@@ -44,6 +45,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
#### Workflow Backend (`backend.yml`)
**Déclencheurs** :
- Branches : `main`, `develop`
- Chemins surveillés :
- `backend/**` : Code Go, migrations, configuration
@@ -52,6 +54,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
- `.github/workflows/backend.yml` : Modifications du workflow lui-même
**Jobs exécutés** :
- **Tests unitaires** : Exécution `go test` sur tous les packages
- **Tests d'intégration** : Utilisation de Testcontainers avec PostgreSQL/PostGIS
- **Tests BDD** : Exécution Godog sur features `api/` et `e2e/`
@@ -65,6 +68,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
#### Workflow Mobile (`mobile.yml`)
**Déclencheurs** :
- Branches : `main`, `develop`
- Chemins surveillés :
- `mobile/**` : Code Flutter/Dart, assets, configuration
@@ -73,6 +77,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
- `.github/workflows/mobile.yml` : Modifications du workflow lui-même
**Jobs exécutés** :
- **Tests unitaires** : Exécution `flutter test` sur widgets et logique métier
- **Tests d'intégration** : Tests d'intégration Flutter (interactions UI complexes)
- **Lint** : Analyse statique `flutter analyze`
@@ -80,6 +85,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
- **Build iOS** : Compilation IPA release sans codesign (dépend des tests)
**Environnement** :
- Tests/Lint/Build Android : Ubuntu latest
- Build iOS : macOS latest (requis pour Xcode)
- Flutter 3.16.0+
@@ -89,6 +95,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
#### Workflow Shared (`shared.yml`)
**Déclencheurs** :
- Branches : `main`, `develop`
- Chemins surveillés :
- `docs/**` : ADR, règles métier, documentation technique
@@ -96,6 +103,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
- `.github/workflows/shared.yml` : Modifications du workflow lui-même
**Jobs exécutés** :
- **Validation documentation** : Build MkDocs en mode strict (détecte erreurs markdown)
- **Vérification liens** : Validation des liens internes/externes dans documentation
- **Tests code partagé** : Exécution tests si du code partagé backend-mobile existe
@@ -117,6 +125,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
| **Commit mixte (backend + mobile + docs)** | ✅ | ✅ | ✅ | ~13 min (parallèle) |
**Économie de temps** :
- Commit backend-only : ~5 min (vs 15 min sans path filters) = **67% plus rapide**
- Commit docs-only : ~30s (vs 15 min) = **97% plus rapide**
@@ -125,6 +134,7 @@ RoadWave est organisé en monorepo contenant backend Go, mobile Flutter, documen
Les tests end-to-end dans `/features/e2e/` **déclenchent les deux workflows** (backend ET mobile) car ils testent l'intégration complète :
**Exemples de features E2E** :
- `features/e2e/abonnements/` : Formulaire mobile → API Zitadel → API RoadWave → Mangopay → Confirmation UI
- `features/e2e/error-handling/` : Perte réseau → Fallback mode offline → Reprise auto après reconnexion
@@ -159,6 +169,7 @@ Les tests end-to-end dans `/features/e2e/` **déclenchent les deux workflows** (
**Complexité initiale** : setup plus complexe que workflow monolithique
**Mitigation** :
- Utiliser des **composite actions** pour partager la config commune
- Documentation claire dans ce ADR
- Coût initial faible (~2h setup) vs gains à long terme importants
@@ -177,15 +188,18 @@ Les tests end-to-end dans `/features/e2e/` **déclenchent les deux workflows** (
### Plan d'Implémentation
**Phase 1** : Setup workflows de base (~1h)
- Créer `backend.yml` avec jobs test + lint + build
- Créer `mobile.yml` avec jobs test + lint + build
- Créer `shared.yml` avec validation docs
**Phase 2** : Configuration path filters (~30 min)
- Ajouter `paths:` à chaque workflow
- Tester avec commits isolés (backend-only, mobile-only, docs-only)
**Phase 3** : Optimisations (~30 min)
- Ajouter caching (Go modules, Flutter dependencies, node_modules)
- Créer composite actions pour config partagée
- Ajouter badges status dans README
@@ -226,6 +240,7 @@ Les tests end-to-end dans `/features/e2e/` **déclenchent les deux workflows** (
⚠️ **Faux négatifs** : path filter mal configuré → test non exécuté → bug en production
**Mitigation** :
- Features E2E déclenchent toujours backend + mobile (safety net)
- Tests de validation dans le plan d'implémentation
- Review obligatoire des modifications de workflows

View File

@@ -6,6 +6,7 @@
## Contexte
Le système de modération RoadWave doit traiter des signalements de contenu audio problématique (haine, spam, droits d'auteur, etc.) avec :
- **SLA stricts** : 2h (critique), 24h (haute), 72h (standard) définis dans [Règle 14](../domains/moderation/rules/moderation-flows.md)
- **Scalabilité** : 0-10K+ signalements/mois
- **Conformité DSA** : transparence, traçabilité, délais garantis
@@ -165,12 +166,14 @@ graph TB
### Dépendances
**Backend Go** :
- `gofiber/fiber/v3` : API Dashboard
- `jackc/pgx/v5` : PostgreSQL + LISTEN/NOTIFY
- `redis/rueidis` : Cache priorisation
- Whisper : via Python subprocess ou go-whisper bindings
**Frontend Dashboard** :
- `react` : Framework UI
- `@tanstack/react-table` : Tables performantes
- `wavesurfer.js` : Player audio avec waveform

View File

@@ -6,6 +6,7 @@
## Contexte
RoadWave nécessite un système de monitoring pour garantir la disponibilité cible 99.9% (SLO) définie dans :
- **Métriques** : latency p99 < 100ms, throughput API, erreurs
- **Alerting** : détection pannes, dégradations performance
- **Incident response** : runbooks, escalation, post-mortems
@@ -93,16 +94,19 @@ graph TB
### Métriques Clés
**API Performance** (requêtes PromQL) :
- Latency p99 : histogramme quantile 99e percentile sur durée requêtes HTTP (fenêtre 5 min)
- Error rate : ratio requêtes 5xx / total requêtes (fenêtre 5 min)
- Throughput : taux de requêtes par seconde (fenêtre 5 min)
**Infrastructure** :
- CPU usage : taux utilisation CPU (mode non-idle, fenêtre 5 min)
- Memory usage : ratio mémoire disponible / totale
- Disk I/O : temps I/O disque (fenêtre 5 min)
**Business** (compteurs custom) :
- Active users (DAU) : `roadwave_active_users_total`
- Audio streams actifs : `roadwave_hls_streams_active`
- Signalements modération : `roadwave_moderation_reports_total`
@@ -182,6 +186,7 @@ graph TB
### Dashboards Grafana
**Dashboard principal** :
- Latency p50/p95/p99 API (5 min, 1h, 24h)
- Error rate 5xx/4xx (seuil alerte >1%)
- Throughput requests/sec
@@ -189,12 +194,14 @@ graph TB
- Business : DAU, streams actifs, signalements modération
**Dashboard PostgreSQL** :
- Slow queries (>100ms)
- Connections actives vs max
- Cache hit ratio (cible >95%)
- Deadlocks count
**Dashboard Redis** :
- Memory usage
- Evictions count
- Commands/sec
@@ -203,27 +210,32 @@ graph TB
### Alerting Rules
**Alertes critiques** (Telegram + Email immédiat) :
- **API Down** : Job API indisponible pendant >1 min → Notification immédiate
- **High Error Rate** : Taux erreurs 5xx >1% pendant >5 min → Notification immédiate
- **Database Down** : PostgreSQL indisponible pendant >1 min → Notification immédiate
**Alertes warnings** (Email uniquement) :
- **High Latency** : Latency p99 >100ms pendant >10 min → Investigation requise
- **Disk Space Running Out** : Espace disque <10% pendant >30 min → Nettoyage requis
### Backup & Disaster Recovery
**PostgreSQL WAL-E** :
- Méthode : Backup continu Write-Ahead Log (WAL)
- Rétention : 7 jours full + WAL incrémentaux
- Stockage : S3 OVH région GRA (France)
- Chiffrement : AES-256 server-side
**RTO (Recovery Time Objective)** : 1h
- Restore depuis S3 : ~30 min (DB 10 GB)
- Validation + relance services : ~30 min
**RPO (Recovery Point Objective)** : 15 min
- Fréquence archivage WAL : toutes les 15 min
- Perte maximale : 15 min de transactions

View File

@@ -6,6 +6,7 @@
## Contexte
RoadWave manipule des données sensibles nécessitant une protection renforcée :
- **Secrets applicatifs** : JWT signing key, DB credentials, Mangopay API keys
- **PII utilisateurs** : Positions GPS précises, emails, données bancaires (via Mangopay)
- **Conformité** : RGPD (minimisation données, encryption at rest), PCI-DSS (paiements)
@@ -88,11 +89,13 @@ graph TB
3. Login root + activation KV-v2 engine (path : `roadwave/`)
**Secrets stockés** :
- **JWT signing key** : Paire RS256 privée/publique
- **Database credentials** : Host, port, user, password (généré aléatoire 32 caractères)
- **Mangopay API** : Client ID, API key, webhook secret
**Récupération depuis Backend Go** :
- Utilisation SDK `hashicorp/vault/api`
- Authentification via token Vault (variable env VAULT_TOKEN)
- Récupération secrets via KVv2 engine
@@ -100,28 +103,33 @@ graph TB
### Encryption PII (Field-level)
**Données chiffrées** (AES-256-GCM) :
- **GPS précis** : lat/lon conservés 24h puis réduits à geohash-5 (~5km²) ([Règle 02](../domains/_shared/rules/rgpd.md))
- **Email** : chiffré en base, déchiffré uniquement à l'envoi
- **Numéro téléphone** : si ajouté (Phase 2)
**Architecture encryption** :
- Utilisation bibliothèque standard Go `crypto/aes` avec mode GCM (AEAD)
- Master key 256 bits (32 bytes) récupérée depuis Vault
- Chiffrement : génération nonce aléatoire + seal GCM → encodage base64
- Stockage : colonne `email_encrypted` en base PostgreSQL
**Contraintes** :
- Index direct sur champ chiffré impossible
- Solution : index sur hash SHA-256 de l'email chiffré pour recherche
### HTTPS/TLS Configuration
**Let's Encrypt wildcard certificate** :
- Méthode : Certbot avec DNS-01 challenge (API OVH)
- Domaines couverts : `roadwave.fr` + `*.roadwave.fr` (wildcard)
- Renouvellement : automatique via cron quotidien (30j avant expiration)
**Nginx TLS configuration** :
- Protocol : TLS 1.3 uniquement (pas de TLS 1.2 ou inférieur)
- Ciphers : TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384
- HSTS : max-age 1 an, includeSubDomains
@@ -214,12 +222,14 @@ graph TB
### Rate Limiting (Protection DDoS/Brute-force)
**Configuration** :
- Middleware Fiber `limiter` avec backend Redis
- Limite : 100 requêtes par minute par IP (global)
- Clé de limitation : adresse IP client
- Réponse limitation : HTTP 429 "Too many requests"
**Rate limits par endpoint** :
- `/auth/login` : 5 req/min/IP (protection brute-force)
- `/moderation/report` : 10 req/24h/user (anti-spam)
- API générale : 100 req/min/IP
@@ -236,6 +246,7 @@ graph TB
| **Encryption master key** | Jamais (re-encryption massive) | Backup sécurisé uniquement |
**Process rotation DB credentials** :
- Vault génère automatiquement nouveau password
- Vault met à jour PostgreSQL avec nouveau password
- Application récupère nouveau password au prochain accès Vault

126
docs/adr/README.md Normal file
View File

@@ -0,0 +1,126 @@
# Architecture Decision Records (ADR)
> Documentation des décisions architecturales importantes du projet RoadWave
## Vue d'ensemble
Les Architecture Decision Records (ADR) documentent les décisions techniques importantes prises au cours du développement de RoadWave. Chaque ADR suit un format standardisé : contexte, décision, alternatives considérées et conséquences.
## Index des ADR
### Core Architecture
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-001](001-langage-backend.md) | Langage Backend | ✅ Accepté | 2025-01-17 |
| [ADR-010](010-architecture-backend.md) | Architecture Backend | ✅ Accepté | 2025-01-25 |
| [ADR-011](011-orm-acces-donnees.md) | ORM et Accès Données | ✅ Accepté | 2025-01-26 |
| [ADR-012](012-frontend-mobile.md) | Frontend Mobile | ✅ Accepté | 2025-01-26 |
| [ADR-014](014-organisation-monorepo.md) | Organisation en Monorepo | ✅ Accepté | 2025-01-28 |
### Data & Infrastructure
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-005](005-base-de-donnees.md) | Base de données | ✅ Accepté | 2025-01-20 |
| [ADR-021](021-solution-cache.md) | Solution de Cache | ✅ Accepté | 2025-02-01 |
| [ADR-015](015-hebergement.md) | Hébergement | ✅ Accepté | 2025-01-28 |
| [ADR-019](019-geolocalisation-ip.md) | Géolocalisation par IP | ✅ Accepté | 2025-01-30 |
### Streaming & Content
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-002](002-protocole-streaming.md) | Protocole Streaming | ✅ Accepté | 2025-01-18 |
| [ADR-003](003-codec-audio.md) | Codec Audio | ✅ Accepté | 2025-01-19 |
| [ADR-004](004-cdn.md) | CDN | ✅ Accepté | 2025-01-20 |
### Security & Auth
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-006](006-chiffrement.md) | Chiffrement | ✅ Accepté | 2025-01-21 |
| [ADR-008](008-authentification.md) | Authentification | ✅ Accepté | 2025-01-24 |
| [ADR-025](025-securite-secrets.md) | Sécurité & Secrets | ✅ Accepté | 2025-02-04 |
### Testing & Quality
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-007](007-tests-bdd.md) | Tests BDD | ✅ Accepté | 2025-01-22 |
| [ADR-013](013-strategie-tests.md) | Stratégie Tests | ✅ Accepté | 2025-01-27 |
| [ADR-022](022-strategie-cicd-monorepo.md) | CI/CD Monorepo | ✅ Accepté | 2025-02-02 |
### Features & Operations
| ADR | Titre | Statut | Date |
|-----|-------|--------|------|
| [ADR-009](009-solution-paiement.md) | Solution Paiement | ✅ Accepté | 2025-01-24 |
| [ADR-016](016-service-emailing.md) | Service Emailing | ✅ Accepté | 2025-01-29 |
| [ADR-017](017-notifications-geolocalisees.md) | Notifications Géolocalisées | ✅ Accepté | 2025-01-30 |
| [ADR-018](018-librairies-go.md) | Librairies Go | ✅ Accepté | 2025-01-30 |
| [ADR-020](020-librairies-flutter.md) | Librairies Flutter | ✅ Accepté | 2025-01-31 |
| [ADR-023](023-architecture-moderation.md) | Architecture Modération | ✅ Accepté | 2025-02-03 |
| [ADR-024](024-monitoring-observabilite.md) | Monitoring & Observabilité | ✅ Accepté | 2025-02-03 |
## Vue d'ensemble technique consolidée
Pour une vue d'ensemble de l'architecture et de la stack technique, consultez [docs/TECHNICAL.md](../TECHNICAL.md).
## Légende des statuts
-**Accepté** : Décision validée et appliquée
- 🟡 **Proposé** : En discussion
- ⏸️ **Suspendu** : Temporairement en attente
-**Rejeté** : Décision rejetée (conservée pour historique)
- 🔄 **Révisé** : Décision modifiée, voir ADR plus récent
## Créer un nouvel ADR
Pour documenter une nouvelle décision architecturale :
1. Créer un fichier `XXX-titre-court.md` (numérotation séquentielle)
2. Utiliser le template suivant :
```markdown
# ADR-XXX : Titre de la décision
**Statut** : Proposé/Accepté/Rejeté
**Date** : YYYY-MM-DD
## Contexte
Pourquoi cette décision est nécessaire ? Quel problème résout-elle ?
## Décision
Quelle solution avons-nous choisie ?
## Alternatives considérées
Quelles autres options ont été évaluées ?
## Conséquences
### Positives
- Avantage 1
- Avantage 2
### Négatives
- Limitation 1
- Compromis accepté
## Références
- Liens vers documentation externe
- Benchmarks
- Articles de référence
```
3. Ajouter l'ADR dans ce fichier README.md et dans `mkdocs.yml`
---
**Dernière mise à jour** : 2026-02-07

View File

@@ -1,320 +0,0 @@
# Diagramme de Séquence : Cache Géospatial Redis
> Architecture du cache Redis Geospatial pour l'optimisation des requêtes de découverte de contenu géolocalisé.
## Vue d'ensemble
Le cache Redis Geospatial permet d'accélérer la recherche de contenus audio à proximité d'une position GPS en évitant des calculs PostGIS coûteux sur PostgreSQL à chaque requête.
**Performance** :
- Sans cache : ~200-500ms (calcul PostGIS sur 100K points)
- Avec cache : ~5-10ms (filtrage Redis en mémoire)
---
## Flux complet : Cold Start → Warm Cache
```mermaid
sequenceDiagram
participant User as 📱 Utilisateur<br/>(Paris)
participant Backend as 🔧 Backend Go
participant Redis as 🔴 Redis Geospatial<br/>(Cache)
participant PostgreSQL as 🗄️ PostgreSQL<br/>+ PostGIS
participant CDN as 🌐 NGINX Cache (OVH VPS)
Note over User,CDN: 🥶 Cold Start - Cache vide
User->>Backend: GET /contents?lat=48.8566&lon=2.3522&radius=50km
Backend->>Redis: EXISTS geo:catalog
Redis-->>Backend: false (cache vide)
Backend->>PostgreSQL: SELECT id, lat, lon, title, geo_level<br/>FROM contents WHERE active=true
Note over PostgreSQL: Tous les contenus actifs<br/>de la plateforme (métadonnées)
PostgreSQL-->>Backend: 100K contenus (métadonnées)
Backend->>Redis: GEOADD geo:catalog<br/>lon1 lat1 "content:1"<br/>lon2 lat2 "content:2"<br/>... (100K entrées)
Note over Redis: Stockage index spatial<br/>en mémoire (~20 MB)
Redis-->>Backend: OK (100000)
Backend->>Redis: EXPIRE geo:catalog 300
Note over Redis: TTL 5 minutes
Backend->>Redis: GEORADIUS geo:catalog<br/>2.3522 48.8566 50 km
Note over Redis: Filtrage spatial instantané<br/>(index geohash)
Redis-->>Backend: [content:123, content:456, ...]<br/>(~500 IDs dans rayon)
Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE id IN (123, 456, ...)<br/>AND geo_level = 'gps_precise'
Note over PostgreSQL: Récupération détails complets<br/>uniquement contenus proches
PostgreSQL-->>Backend: Détails complets (500 contenus GPS)
Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE city='Paris' OR dept='75'<br/>OR region='IDF' OR geo_level='national'
Note over PostgreSQL: Contenus par niveau géographique
PostgreSQL-->>Backend: Contenus ville/région/national
Backend->>Backend: Scoring & mixage :<br/>- GPS proche : 70%<br/>- Ville : 20%<br/>- Région : 8%<br/>- National : 2%
Backend-->>User: JSON: [{id, title, creator, audioUrl, score}, ...]<br/>(playlist mixée et scorée)
Note over User,CDN: 🎵 Lecture audio (requêtes séparées)
User->>CDN: GET /audio/content-123.m3u8
CDN-->>User: Playlist HLS
User->>CDN: GET /audio/content-123-segment-001.ts
CDN-->>User: Segment audio Opus
Note over User,CDN: 🔥 Warm Cache - Utilisateur 2 à Lyon (45km+)
participant User2 as 📱 Utilisateur 2<br/>(Lyon)
User2->>Backend: GET /contents?lat=45.7640&lon=4.8357&radius=50km
Backend->>Redis: EXISTS geo:catalog
Redis-->>Backend: true ✅ (cache chaud)
Backend->>Redis: GEORADIUS geo:catalog<br/>4.8357 45.7640 50 km
Note over Redis: Filtrage instantané<br/>sur cache existant
Redis-->>Backend: [content:789, content:012, ...]<br/>(~300 IDs différents)
Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE id IN (789, 012, ...)<br/>AND geo_level = 'gps_precise'
PostgreSQL-->>Backend: Détails complets
Backend->>PostgreSQL: SELECT * FROM contents<br/>WHERE city='Lyon' OR dept='69'<br/>OR region='Auvergne-RA' OR geo_level='national'
PostgreSQL-->>Backend: Contenus ville/région/national
Backend->>Backend: Scoring & mixage
Backend-->>User2: JSON: [{id, title, creator, audioUrl, score}, ...]
```
---
## Stratégie de cache
### Cache du catalogue complet (approche choisie)
**Principe** : Au premier cache miss, charger **TOUS** les contenus géolocalisés en une seule fois dans Redis.
**Avantages** :
- ✅ 1 seul cache miss au démarrage de l'instance
- ✅ Toutes les requêtes suivantes servies par Redis (n'importe quelle position GPS)
- ✅ Simple à gérer (1 seule clé Redis : `geo:catalog`)
- ✅ Pas de duplication de données
**Inconvénients** :
- ⚠️ Premier utilisateur subit le cold start (~500ms-1s)
- ⚠️ Nécessite charger toute la base (acceptable : ~20 MB pour 100K contenus)
**Alternatives non retenues** :
- Cache par zone géographique → cache miss fréquents, complexité gestion chevauchements
- Cache à la demande → trop de cache miss, pas d'optimisation réelle
---
## Détails techniques
### 1. Données stockées dans Redis
**Clé Redis** : `geo:catalog`
**Structure** :
```
GEOADD geo:catalog
2.3522 48.8566 "content:12345" # lon, lat, member
4.8357 45.7640 "content:67890"
...
```
**Taille mémoire** :
- ~200 bytes par entrée (ID + coordonnées + index geohash)
- 100K contenus = ~20 MB
- Négligeable pour Redis (plusieurs GB RAM disponibles)
**TTL** : 5 minutes (300 secondes)
- Le contenu géolocalisé est quasi-statique (change peu)
- Rechargement automatique toutes les 5 minutes si cache expiré
- Permet de propager les nouveaux contenus rapidement
### 2. Niveaux géographiques
Le cache Redis ne contient que les contenus avec **GPS précis** (`geo_level = 'gps_precise'`).
Les autres niveaux géographiques sont gérés par filtrage applicatif :
| Niveau | Stockage | Requête |
|--------|----------|---------|
| **GPS précis** | Redis + PostgreSQL | `GEORADIUS` puis `SELECT WHERE id IN (...)` |
| **Ville** | PostgreSQL uniquement | `SELECT WHERE city = ?` |
| **Département** | PostgreSQL uniquement | `SELECT WHERE department = ?` |
| **Région** | PostgreSQL uniquement | `SELECT WHERE region = ?` |
| **National** | PostgreSQL uniquement | `SELECT WHERE geo_level = 'national'` |
### 3. Commandes Redis utilisées
```bash
# Vérifier existence du cache
EXISTS geo:catalog
# Retour : 0 (n'existe pas) ou 1 (existe)
# Charger le catalogue complet (cold start)
GEOADD geo:catalog 2.3522 48.8566 "content:1" 4.8357 45.7640 "content:2" ...
# Retour : nombre d'éléments ajoutés
# Définir TTL 5 minutes
EXPIRE geo:catalog 300
# Rechercher contenus dans un rayon
GEORADIUS geo:catalog 2.3522 48.8566 50 km
# Retour : ["content:123", "content:456", ...]
# Optionnel : obtenir distance et coordonnées
GEORADIUS geo:catalog 2.3522 48.8566 50 km WITHDIST WITHCOORD
# Retour : [["content:123", "12.5", ["2.35", "48.85"]], ...]
```
### 4. Algorithme de scoring
Le backend mixe les résultats selon une pondération :
```
Score final =
(Pertinence GPS × 0.70) +
(Pertinence Ville × 0.20) +
(Pertinence Région × 0.08) +
(Pertinence National × 0.02)
```
**Critères de pertinence** :
- **GPS** : Plus proche = score élevé (distance inversée)
- **Ville/Région** : Matching exact = score maximal
- **National** : Score fixe faible (contenu générique)
**Mixage playlist** :
1. Trier tous les contenus par score décroissant
2. Appliquer diversité créateurs (pas 3 contenus du même créateur d'affilée)
3. Injecter contenus sponsorisés/mis en avant (futurs)
4. Retourner top 50 pour la session d'écoute
---
## Métriques de performance
### Temps de réponse typiques
| Scénario | Latence | Détail |
|----------|---------|--------|
| **Cold start** | 500-1000ms | Chargement 100K contenus dans Redis + requête |
| **Warm cache** | 5-10ms | `GEORADIUS` + `SELECT WHERE id IN (...)` |
| **TTL expiré** | 500-1000ms | Rechargement automatique |
### Charge serveurs
| Composant | Sans cache | Avec cache |
|-----------|------------|------------|
| **PostgreSQL CPU** | 60-80% | 10-20% |
| **Redis CPU** | N/A | 5-15% |
| **Throughput** | ~50 req/s | ~500 req/s |
---
## Cas limites et optimisations futures
### Cas limite 1 : Contenu très dense (Paris intra-muros)
**Problème** : 10K contenus dans rayon 5km → trop de résultats
**Solution actuelle** :
- Limiter résultats Redis à 1000 premiers (tri par distance)
- Scorer et filtrer côté application
**Optimisation future** :
- Ajuster rayon dynamiquement selon densité
- Utiliser `GEORADIUS ... COUNT 500` pour limiter côté Redis
### Cas limite 2 : Zones rurales (peu de contenu)
**Problème** : Rayon 50km retourne <10 contenus
**Solution actuelle** :
- Augmenter poids contenus région/national dans le scoring
- Suggérer contenus nationaux populaires
**Optimisation future** :
- Augmenter rayon automatiquement jusqu'à obtenir min 20 contenus
- `GEORADIUS ... 100 km` si rayon initial insuffisant
### Cas limite 3 : Nombreux créateurs actifs (évolutivité)
**Problème** : Cache 100K → 1M contenus (200 MB Redis)
**Solution actuelle** :
- 200 MB reste acceptable pour Redis
**Optimisation future** :
- Sharding géographique : cache Europe, cache USA, etc.
- Limiter cache aux contenus actifs 90 derniers jours
---
## Invalidation du cache
### Stratégies d'invalidation
| Événement | Action cache | Détail |
|-----------|--------------|--------|
| **Nouveau contenu publié** | Lazy (TTL) | Visible sous 5 minutes max |
| **Contenu supprimé/modéré** | Lazy (TTL) | Disparaît sous 5 minutes max |
| **Mise à jour GPS contenu** | Lazy (TTL) | Nouvelle position sous 5 minutes |
| **Déploiement backend** | Flush volontaire | `DEL geo:catalog` si schema change |
**Pas d'invalidation immédiate** pour simplifier l'architecture (cohérence éventuelle acceptable).
**Alternative future** :
- Pub/Sub Redis : notifier toutes les instances backend lors d'un changement
- `GEOADD geo:catalog 2.35 48.85 "content:new"` pour ajout immédiat
---
## Schéma simplifié
```
┌─────────────────────────────────────────────────────────┐
│ Utilisateur │
│ (Position GPS actuelle) │
└────────────────────────┬────────────────────────────────┘
│ GET /contents?lat=X&lon=Y&radius=Z
┌─────────────────────────────────────────────────────────┐
│ Backend Go (Fiber) │
│ │
│ 1. Vérifier cache Redis │
│ ├─ Cache HIT → GEORADIUS rapide │
│ └─ Cache MISS → Charger catalogue complet │
│ │
│ 2. Filtrer contenus GPS proches (Redis) │
│ 3. Récupérer contenus ville/région/national (PG) │
│ 4. Scorer et mixer selon pondération │
│ 5. Retourner playlist │
└────────────┬───────────────────────────┬────────────────┘
│ │
│ GEORADIUS │ SELECT détails
▼ ▼
┌─────────────────────┐ ┌───────────────────────────┐
│ Redis Geospatial │ │ PostgreSQL + PostGIS │
│ (Index spatial) │ │ (Données complètes) │
│ │ │ │
│ • geo:catalog │ │ • contents (détails) │
│ • TTL 5 min │ │ • users │
│ • ~20 MB mémoire │ │ • playlists │
└─────────────────────┘ └───────────────────────────┘
```
---
## Références
- [ADR-005 : Base de données](../../adr/005-base-de-donnees.md)
- [Redis Geospatial Commands](https://redis.io/docs/data-types/geospatial/)
- [PostGIS Documentation](https://postgis.net/documentation/)
- [Règles métier : Algorithme de recommandation](../../domains/recommendation/rules/algorithme-recommandation.md)
- [Règles métier : Centres d'intérêt](../../domains/recommendation/rules/centres-interet-jauges.md)

View File

@@ -7,6 +7,7 @@
## Contexte
RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android Auto) avec :
- Contenu généré par utilisateurs (UGC)
- Monétisation : publicités géolocalisées + Premium (4.99€ web / 5.99€ IAP)
- GPS en arrière-plan
@@ -15,6 +16,7 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
## Stratégie de conformité
**Approche multi-plateforme** avec :
- Modération UGC robuste (IA + humain)
- Prix différenciés selon région (US/EU/Monde)
- GPS avec disclosure complète
@@ -32,6 +34,7 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
## Conformité détaillée
### Android Auto / CarPlay ✅
- 100% audio (pas de vidéo)
- Commandes standard au volant
- Aucun achat in-car
@@ -42,16 +45,19 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
### Google Play ⚠️
**UGC (critique)** :
- Modération hybride IA + humain ✅
- 3 premiers contenus validés manuellement ✅
- Système de strikes (4 = ban) ✅
- Signalement + blocage utilisateurs ✅
**GPS Background (critique)** :
- Permission "Always Location" = **OPTIONNELLE**
- Demandée uniquement pour mode piéton (notifications arrière-plan audio-guides)
- Justification Play Console :
> "RoadWave permet aux utilisateurs de recevoir des alertes audio-guides lorsqu'ils passent à pied près de monuments/musées, même quand l'app est en arrière-plan. Cette fonctionnalité est optionnelle et peut être désactivée dans les paramètres."
- In-app disclosure obligatoire (écran dédié avant demande permission)
- Si refusée : app fonctionne en mode voiture uniquement
- **Action** : Remplir formulaire background location Play Console avec justification
@@ -68,19 +74,23 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
### App Store ⚠️
**Prix différenciés (légaux depuis 2025-2026)** :
- 🇺🇸 US : Lien externe autorisé (0% commission)
- 🇪🇺 EU : Paiement externe DMA (7-20% commission réduite)
- 🌍 Monde : IAP obligatoire (30% commission)
**UGC** :
- Mode Kids obligatoire (filtrage selon âge) ✅
- Système de modération + signalement ✅
**GPS Background (critique)** :
- Permission "Always Location" = **OPTIONNELLE**
- Deux strings Info.plist requises :
- `NSLocationWhenInUseUsageDescription` : explication mode voiture
- `NSLocationAlwaysAndWhenInUseUsageDescription` : explication mode piéton (optionnel)
- In-app disclosure obligatoire avant demande "Always"
- Flux two-step : When In Use → Always (si user active mode piéton)
- Si refusée : app fonctionne en mode voiture uniquement
@@ -89,6 +99,7 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
### Revenus créateurs
**Position** : Paiements créateurs = "services" (comme YouTube/Uber), pas IAP
- Paiement via Mangopay Connect (externe)
- Commission stores uniquement sur Premium (IAP)
- Comparables : YouTube AdSense, TikTok Creator Fund, Uber
@@ -110,12 +121,14 @@ RoadWave est une app audio géolocalisée utilisée en conduite (CarPlay/Android
## Stratégie de lancement
**Phase 1 - MVP** :
- IAP uniquement (5.99€/mois mondial)
- Modération UGC active
- GPS avec disclosure
- CarPlay/Android Auto basique
**Phase 2 - Post-validation** :
- Prix différenciés US (lien externe 4.99€)
- Paiement externe EU (DMA)
- Monétisation créateurs (Mangopay)

View File

@@ -0,0 +1,14 @@
FROM squidfunk/mkdocs-material:latest
# Install mkdocs-kroki-plugin
RUN pip install --no-cache-dir mkdocs-kroki-plugin
# Set working directory
WORKDIR /docs
# Expose MkDocs port
EXPOSE 8000
# Default command
ENTRYPOINT ["mkdocs"]
CMD ["serve", "--dev-addr=0.0.0.0:8000"]

View File

@@ -18,4 +18,4 @@ RUN pip install --no-cache-dir \
WORKDIR /docs
ENTRYPOINT ["python3", "/docs/scripts/generate-pdf-docs.py"]
ENTRYPOINT ["python3", "/docs/docs/scripts/generate-pdf-docs.py"]

View File

@@ -200,14 +200,6 @@ domains/<domain>/
└── features/ # Tests BDD Gherkin (*.feature)
```
## Navigation
- *(structure legacy, déprécié)*
- [🏛️ ADR (Architecture Decision Records)](../adr/)
- [⚖️ Documentation légale](../legal/README.md)
- [🖥️ Interfaces UI](../interfaces/README.md)
- [🔧 Documentation technique](../technical.md)
## Ubiquitous Language Global
**Termes transversaux utilisés dans tous les domaines** :

View File

@@ -19,11 +19,12 @@ Le domaine **Shared** constitue le **Core Domain** de RoadWave. Il contient les
## Modèle de données
- [Diagramme entités globales](entities/../entities/modele-global.md) - Entités centrales : USERS, CONTENTS, SUBSCRIPTIONS, LISTENING_HISTORY
- [Diagramme entités globales](entities/../entities/vue-ensemble.md) - Entités centrales : USERS, CONTENTS, SUBSCRIPTIONS, LISTENING_HISTORY
## Ubiquitous Language
**Termes métier du domaine partagé** :
- **User** : Utilisateur de la plateforme (auditeur, créateur, ou les deux)
- **Content** : Tout contenu audio diffusé sur la plateforme
- **Subscription** : Abonnement d'un utilisateur à un créateur ou une catégorie

View File

@@ -0,0 +1,57 @@
# Account Deletions
📖 Suppressions de compte avec grace period 30 jours (Article 17 RGPD)
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
email varchar(255)
status varchar(20)
}
Table account_deletions {
id uuid [primary key]
user_id uuid [not null, unique, ref: - users.id, note: 'One-to-one: un user ne peut avoir qu une seule demande active']
status deletion_status_enum [not null, default: 'pending']
cancellation_token varchar(64) [unique, note: 'Token dans email pour annuler (expire après 30j)']
requested_at timestamp [not null, default: `now()`]
effective_at timestamp [not null, note: 'Auto-calculated: requested_at + 30 days']
cancelled_at timestamp [note: 'Timestamp annulation via lien email (NULL si non annulé)']
deleted_at timestamp [note: 'Timestamp suppression effective (NULL si pending/cancelled)']
deletion_reason text [note: 'Raison optionnelle fournie par l utilisateur']
deleted_data_summary jsonb [note: 'Résumé des données supprimées (audit trail)']
indexes {
(user_id) [unique]
(status, effective_at) [note: 'Daily cron job: WHERE status = pending AND effective_at < NOW()']
(cancellation_token) [unique]
}
}
Enum deletion_status_enum {
pending [note: 'Grace period actif (30j), compte désactivé, annulation possible']
cancelled [note: 'Utilisateur a annulé via lien email']
completed [note: 'Suppression effective réalisée après 30j']
}
```
## Légende
**Statuts** :
- `pending`: Grace period actif (30j), compte désactivé, annulation possible
- `cancelled`: Utilisateur a annulé via lien email
- `completed`: Suppression effective réalisée après 30j
**Processus** :
1. Demande → compte désactivé, contenus cachés
2. Email avec `cancellation_token` (valide 30j)
3. Si annulation → `status = cancelled`, compte réactivé
4. Si 30j écoulés → job cron supprime données, anonymise contenus
**Données supprimées** :
- Profil utilisateur, historique GPS/écoute, sessions
- Contenus créés : anonymisés (`créateur = "Utilisateur supprimé"`)

View File

@@ -0,0 +1,78 @@
# Breach Incidents
📖 Registre violations de données (Article 33 RGPD)
## Diagramme
```kroki-dbml
Table breach_incidents {
id uuid [primary key]
severity breach_severity_enum [not null]
description text [not null, note: 'Description détaillée de l incident']
data_categories_affected jsonb [not null, note: 'Array: ["gps", "email", "listening_history"]']
estimated_users_count int [not null, note: 'Estimation nombre users impactés']
detected_at timestamp [not null, default: `now()`, note: 'H+0: Détection initiale']
contained_at timestamp [note: 'Timestamp confinement de la faille']
cnil_notified_at timestamp [note: 'H+48: Notification CNIL si requis']
users_notified_at timestamp [note: 'H+72: Notification users si risque élevé']
mitigation_actions text [note: 'Actions correctives mises en place']
cnil_notification_required boolean [not null, default: false]
user_notification_required boolean [not null, default: false]
indexes {
(severity, detected_at) [note: 'Incidents par gravité et chronologie']
(cnil_notification_required, cnil_notified_at) [note: 'Track CNIL notification compliance']
}
}
Table users {
id uuid [primary key]
}
Table breach_affected_users {
id uuid [primary key]
breach_id uuid [not null, ref: > breach_incidents.id]
user_id uuid [not null, ref: > users.id]
notified_at timestamp [note: 'Timestamp notification user (NULL si pas encore notifié)']
notification_channel notification_channel_enum [note: 'Canal utilisé pour notifier']
indexes {
(breach_id, user_id) [unique, note: 'Un user ne peut être listé qu une fois par incident']
(breach_id, notified_at) [note: 'Track notification progress']
(user_id) [note: 'Historique incidents pour un user']
}
}
Enum breach_severity_enum {
low [note: 'Pas de notification requise (mesures techniques suffisantes)']
medium [note: 'Notification CNIL uniquement']
high [note: 'Notification CNIL + utilisateurs']
critical [note: 'Notification immédiate tous canaux + SMS fondateur']
}
Enum notification_channel_enum {
email [note: 'Email notification']
push [note: 'Push notification mobile']
sms [note: 'SMS (critical only)']
}
```
## Légende
**Sévérité** :
- `low`: Pas de notification requise (mesures techniques suffisantes)
- `medium`: Notification CNIL uniquement
- `high`: Notification CNIL + utilisateurs
- `critical`: Notification immédiate tous canaux + SMS fondateur
**Timeline 72h** :
- H+0 : Détection, confinement
- H+24 : Évaluation gravité
- H+48 : Notification CNIL si requis
- H+72 : Notification utilisateurs si risque élevé
**Catégories de données** :
- `data_categories_affected`: JSON `["gps", "email", "listening_history"]`

View File

@@ -0,0 +1,56 @@
# User Consents
📖 Consentements RGPD avec historique et versioning
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
}
Table user_consents {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
consent_type consent_type_enum [not null]
consent_version varchar(10) [not null, note: 'Format: v1.0, v2.0, etc.']
accepted boolean [not null, note: 'true = opted-in, false = opted-out']
given_at timestamp [not null, default: `now()`]
ip_address inet [not null, note: 'Proof of consent for CNIL audits']
user_agent text [not null, note: 'Device/browser proof']
indexes {
(user_id, consent_type, consent_version) [note: 'Latest consent per type']
(user_id, given_at) [note: 'Consent history timeline']
}
}
Enum consent_type_enum {
geolocation_precise [note: 'Géolocalisation GPS précise (obligatoire pour contenu hyperlocal)']
analytics [note: 'Analytics Matomo (optionnel)']
push_notifications [note: 'Notifications push (optionnel)']
cookies_analytics [note: 'Cookies analytiques (optionnel)']
}
```
## Légende
**Types de consentement** :
- `geolocation_precise` : Géolocalisation GPS précise (obligatoire pour contenu hyperlocal)
- `analytics` : Analytics Matomo (optionnel)
- `push_notifications` : Notifications push (optionnel)
- `cookies_analytics` : Cookies analytiques (optionnel)
**Versioning** :
- Chaque changement de CGU/politique = nouvelle version
- Historique complet conservé (preuve légale)
- Format version : `v1.0`, `v2.0`, etc.
**Conformité RGPD** :
- Granularité : fonctionnel / analytique / marketing
- Consentement libre et éclairé
- Révocable à tout moment
- Historique = preuve en cas de contrôle CNIL

View File

@@ -0,0 +1,56 @@
# Data Retention Logs
📖 Logs purges automatiques inactivité (Article 5 RGPD - Minimisation)
## Diagramme
```kroki-dbml
Table data_retention_logs {
id uuid [primary key]
action_type retention_action_enum [not null]
users_processed int [not null, default: 0, note: 'Nombre total users analysés']
users_warned int [not null, default: 0, note: 'Nombre users notifiés (90j/30j/7j)']
users_deleted int [not null, default: 0, note: 'Nombre users supprimés effectivement']
details jsonb [note: 'Détails: threshold_date, user_ids_deleted, notifications_sent']
executed_at timestamp [not null, default: `now()`, note: 'Timestamp exécution du job cron']
execution_duration_ms bigint [not null, note: 'Durée d exécution en millisecondes']
indexes {
(action_type, executed_at) [note: 'Historique jobs par type']
(executed_at) [note: 'Timeline complète des jobs']
}
}
Enum retention_action_enum {
check_inactive [note: 'Vérification quotidienne comptes inactifs > 5 ans']
send_warnings [note: 'Envoi notifications (90j/30j/7j avant suppression)']
delete_accounts [note: 'Suppression effective comptes inactifs']
}
```
## Légende
**Action types** :
- `check_inactive`: Vérification quotidienne comptes inactifs > 5 ans
- `send_warnings`: Envoi notifications (90j/30j/7j avant suppression)
- `delete_accounts`: Suppression effective comptes inactifs
**Règles de conservation** :
- Auditeur : 5 ans inactivité → suppression
- Créateur actif : jamais (tant que contenus écoutés)
- Créateur inactif : 5 ans + 2 ans sans écoute → suppression
**Details JSON** :
```json
{
"threshold_date": "2021-02-08",
"user_ids_deleted": ["uuid1", "uuid2"],
"notifications_sent": {
"90_days": 15,
"30_days": 8,
"7_days": 3
}
}
```

View File

@@ -0,0 +1,65 @@
# Devices
📖 Appareils de confiance et gestion multi-device
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
}
Table devices {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
device_name varchar(255) [note: 'User-defined device name']
os varchar(50) [note: 'iOS, Android, Windows, macOS, Linux']
browser varchar(50) [note: 'Safari, Chrome, Firefox, etc.']
device_type device_type_enum [not null, note: 'mobile, tablet, desktop, car']
is_trusted boolean [not null, default: false, note: 'Bypass 2FA for 30 days if true']
trusted_until timestamp [note: 'NULL if not trusted, expires after 30 days']
first_seen_at timestamp [not null, default: `now()`]
last_seen_at timestamp [not null, default: `now()`]
last_ip inet [not null]
last_city varchar(100)
last_country_code char(2)
indexes {
(user_id, last_seen_at) [note: 'List user devices by recent activity']
(user_id, is_trusted) [note: 'Find trusted devices for user']
}
}
Table sessions {
id uuid [primary key]
device_id uuid [ref: > devices.id]
}
Enum device_type_enum {
mobile [note: 'Smartphone Android/iOS']
tablet [note: 'Tablette']
desktop [note: 'Ordinateur']
car [note: 'Système embarqué (CarPlay/Android Auto)']
}
```
## Légende
**Types d'appareil** :
- `mobile` : Smartphone Android/iOS
- `tablet` : Tablette
- `desktop` : Ordinateur
- `car` : Système embarqué (CarPlay/Android Auto)
**Appareil de confiance** :
- Option "Ne plus demander sur cet appareil" → bypass 2FA pendant **30 jours**
- Révocable depuis paramètres compte
- Liste des appareils de confiance visible
**Sécurité** :
- Détection automatique nouveau device → notification push + email
- Localisation suspecte (pays différent) → alerte
- Révocation individuelle ou globale possible

View File

@@ -0,0 +1,77 @@
# Data Exports
📖 Exports de données utilisateur (portabilité RGPD Article 20)
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
}
Table data_exports {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
status export_status_enum [not null, default: 'pending']
export_url varchar(512) [note: 'S3/CDN signed URL (NULL until generated)']
size_bytes bigint [note: 'File size in bytes (NULL until generated)']
format export_format_enum [not null, default: 'json']
requested_at timestamp [not null, default: `now()`]
generated_at timestamp [note: 'When export file was created (NULL if pending/generating)']
expires_at timestamp [note: 'Auto-calculated: generated_at + 7 days']
downloaded_at timestamp [note: 'First download timestamp (NULL if not yet downloaded)']
indexes {
(user_id, requested_at) [note: 'User export history']
(status, requested_at) [note: 'Background worker queue (WHERE status = pending)']
(expires_at) [note: 'Daily cleanup job (DELETE WHERE expires_at < NOW())']
}
}
Enum export_status_enum {
pending [note: 'Demande en file d attente']
generating [note: 'Génération en cours (worker background)']
ready [note: 'Export disponible au téléchargement']
downloaded [note: 'Export téléchargé par l utilisateur']
expired [note: 'Export expiré (supprimé automatiquement)']
}
Enum export_format_enum {
json [note: 'Machine-readable (données brutes)']
html [note: 'Human-readable (page web stylée)']
zip [note: 'Archive complète (JSON + HTML + audio files)']
}
```
## Légende
**Formats d'export** :
- `json` : Machine-readable (données brutes)
- `html` : Human-readable (page web stylée)
- `zip` : Archive complète (JSON + HTML + audio files)
**Contenu de l'export** :
- Profil utilisateur (email, pseudo, date inscription, bio)
- Historique d'écoute (titres, dates, durées)
- Contenus créés (audio + métadonnées)
- Abonnements et likes
- Centres d'intérêt (jauges)
- Historique consentements RGPD
**Statuts** :
- `pending` : Demande en file d'attente
- `generating` : Génération en cours (worker background)
- `ready` : Export disponible au téléchargement
- `downloaded` : Export téléchargé par l'utilisateur
- `expired` : Export expiré (supprimé automatiquement)
**Règles** :
- Génération asynchrone (worker background)
- Délai max : **48h** (conformité RGPD)
- Conservation : **7 jours** après génération
- Limite : **1 export/mois** (anti-abus)
- Notification par email avec lien de téléchargement

View File

@@ -0,0 +1,67 @@
# Interest Gauges
📖 Jauges de centres d'intérêt dynamiques pour recommandation personnalisée
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
}
Table interest_gauges {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
category interest_category_enum [not null]
score decimal(5,2) [not null, default: 0, note: 'Range: 0.00 to 100.00']
last_updated timestamp [not null, default: `now()`]
interactions_count int [not null, default: 0, note: 'Total interactions for this category']
indexes {
(user_id, category) [unique, note: 'One gauge per user per category']
(user_id, score) [note: 'Order categories by score for recommendations']
}
}
Enum interest_category_enum {
automobile [note: 'Voitures, mécanique, course automobile']
travel [note: 'Voyages, tourisme, découverte']
music [note: 'Musique, concerts, artistes']
news [note: 'Actualités, politique, économie']
sport [note: 'Sports, événements sportifs']
culture [note: 'Cinéma, livres, expositions']
food [note: 'Gastronomie, restaurants, recettes']
tech [note: 'Technologie, innovation, gadgets']
history [note: 'Histoire, patrimoine, musées']
nature [note: 'Nature, randonnée, écologie']
}
```
## Légende
**Catégories** :
- `automobile` : Voitures, mécanique, course automobile
- `travel` : Voyages, tourisme, découverte
- `music` : Musique, concerts, artistes
- `news` : Actualités, politique, économie
- `sport` : Sports, événements sportifs
- `culture` : Cinéma, livres, expositions
- `food` : Gastronomie, restaurants, recettes
- `tech` : Technologie, innovation, gadgets
- `history` : Histoire, patrimoine, musées
- `nature` : Nature, randonnée, écologie
**Score** :
- Échelle : **0-100**
- Augmentation : +2% par like, +5% par abonnement créateur
- Diminution : -1% par skip rapide (<30s), -5% par signalement
- Calcul combiné : Distance GPS + matching intérêts
**Algorithme recommandation** :
- **70% géolocalisation** : Proximité GPS
- **30% centres d'intérêt** : Score jauges
- Boost si créateur suivi : +0.3 au score final
- Limite : 6 contenus/heure pour éviter spam

View File

@@ -0,0 +1,66 @@
# Location History
📖 Historique de géolocalisation avec anonymisation automatique
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
}
Table location_history {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
location geography [note: 'PostGIS geography type: POINT with SRID 4326 (WGS84)']
geohash varchar(12) [note: 'Precision 5 geohash (~5km²) after anonymization']
anonymized boolean [not null, default: false, note: 'true after 24h auto-anonymization']
context location_context_enum [not null]
speed_kmh float [note: 'GPS speed in km/h (NULL if stationary)']
accuracy_meters float [not null, note: 'GPS accuracy radius in meters']
created_at timestamp [not null, default: `now()`]
anonymized_at timestamp [note: 'When precise location was replaced by geohash']
indexes {
(user_id, created_at) [note: 'User location timeline']
(created_at, anonymized) [note: 'Daily anonymization job (WHERE anonymized = false AND created_at < NOW() - 24h)']
(location) [type: gist, note: 'PostGIS spatial index for proximity queries']
(geohash) [note: 'Analytics queries on anonymized data']
}
}
Enum location_context_enum {
listening [note: 'Position pendant écoute de contenu']
search [note: 'Position lors d une recherche']
background [note: 'Tracking en arrière-plan']
manual [note: 'Position partagée manuellement']
}
```
## Légende
**Anonymisation progressive** :
- Données précises conservées **24h** (recommandation personnalisée)
- Après 24h : conversion en **geohash précision 5** (~5km²)
- Coordonnées originales supprimées définitivement
- Job quotidien PostGIS automatique
**Exceptions** :
- Historique personnel visible (liste trajets) : conservation intégrale tant que compte actif
- Analytics globales : uniquement geohash anonyme
- Suppression complète si suppression du compte
**Contexte** :
- `listening` : Position pendant écoute de contenu
- `search` : Position lors d'une recherche
- `background` : Tracking en arrière-plan
- `manual` : Position partagée manuellement
**Conformité RGPD** :
- Vraie anonymisation (CNIL compliant)
- Permet analytics agrégées (heatmaps trafic)
- PostGIS natif, 0€

View File

@@ -1,69 +0,0 @@
# Modèle de données - Entités globales
📖 Entités de base utilisées dans tous les domaines métier
## Diagramme
```mermaid
erDiagram
USERS ||--o{ CONTENTS : "crée"
USERS ||--o{ SUBSCRIPTIONS : "s'abonne à"
USERS ||--o{ LISTENING_HISTORY : "écoute"
CONTENTS ||--o{ LISTENING_HISTORY : "écouté"
CONTENTS }o--|| USERS : "créé par"
USERS {
uuid id PK
string email UK
string pseudo UK
date birthdate
string role
timestamp created_at
boolean email_verified
}
CONTENTS {
uuid id PK
uuid creator_id FK
string title
string audio_url
string status
string age_rating
string geo_type
point geo_location
string[] tags
int duration_seconds
timestamp published_at
boolean is_moderated
}
SUBSCRIPTIONS {
uuid id PK
uuid subscriber_id FK
uuid creator_id FK
timestamp subscribed_at
}
LISTENING_HISTORY {
uuid id PK
uuid user_id FK
uuid content_id FK
uuid creator_id FK
boolean is_subscribed
decimal completion_rate
int last_position_seconds
string source
boolean auto_like
timestamp listened_at
}
```
## Légende
**Entités de base** :
- **USERS** : Utilisateurs plateforme - Rôles : `listener`, `creator`, `moderator`, `admin`
- **CONTENTS** : Contenus audio - Status : `draft`, `pending_review`, `published`, `moderated`, `deleted` - Geo-type : `geo_ancre` (70% geo), `geo_contextuel` (50% geo), `geo_neutre` (20% geo) - Age rating : `all`, `13+`, `16+`, `18+`
- **SUBSCRIPTIONS** : Abonnements créateurs - Utilisé pour filtrer recommandations et calculer engagement
- **LISTENING_HISTORY** : Historique écoutes - Source : `recommendation`, `search`, `direct_link`, `profile`, `history`, `live_notification`, `audio_guide` - Utilisé pour scoring recommandation et statistiques créateur

View File

@@ -0,0 +1,61 @@
# Parental Consents
📖 Consentements parentaux pour utilisateurs 13-15 ans (Article 8 RGPD)
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
birthdate date [not null]
}
Table parental_consents {
id uuid [primary key]
user_id uuid [not null, unique, ref: > users.id, note: 'Ado 13-15 ans (1 consent par user max)']
parent_email varchar(255) [not null, note: 'Email du parent pour validation']
validation_token varchar(64) [unique, note: 'Token de validation envoyé par email (expire 7j)']
validated boolean [not null, default: false, note: 'true après clic parent sur lien email']
token_expires_at timestamp [not null, note: 'validation_token expire après 7 jours']
validated_at timestamp [note: 'Timestamp de validation parent (NULL si non validé)']
parent_ip inet [note: 'IP du parent lors de la validation']
parent_user_agent text [note: 'User agent parent (preuve validation)']
revoked_at timestamp [note: 'Révocation du consentement parental']
revocation_reason text [note: 'Raison de la révocation (optionnel)']
indexes {
(user_id) [unique, note: 'Un seul consentement parental actif par user']
(validation_token) [unique, note: 'Lookup rapide pour validation lien email']
(validated, token_expires_at) [note: 'Cleanup des tokens expirés non validés']
}
}
Table parental_controls {
id uuid [primary key]
parental_consent_id uuid [not null, unique, ref: - parental_consents.id, note: 'One-to-one relationship']
gps_enabled boolean [not null, default: false, note: 'Autoriser GPS précis (false = GeoIP uniquement)']
messaging_enabled boolean [not null, default: false, note: 'Autoriser messagerie privée']
content_16plus_enabled boolean [not null, default: false, note: 'Autoriser contenu 16+']
weekly_digest_config jsonb [note: 'Config notifications hebdo parent (email, contenu, format)']
updated_at timestamp [not null, default: `now()`]
indexes {
(parental_consent_id) [unique]
}
}
```
## Légende
**Workflow** :
1. Ado saisit email parent → `validation_token` généré (expire 7j)
2. Parent clique lien → `validated = true`
3. Parent configure `PARENTAL_CONTROLS`
4. Révocation possible → `revoked_at` renseigné
**Restrictions par défaut (13-15 ans)** :
- `gps_enabled`: `false` (GeoIP uniquement)
- `messaging_enabled`: `false`
- `content_16plus_enabled`: `false`
- Dashboard parent : notifications hebdomadaires activité

View File

@@ -0,0 +1,54 @@
# Privacy Policy Versions
📖 Versioning politique de confidentialité (Articles 13-14 RGPD)
## Diagramme
```kroki-dbml
Table privacy_policy_versions {
id uuid [primary key]
version varchar(10) [not null, unique, note: 'Format: v1.0, v2.0, etc.']
content_markdown text [not null, note: 'Source: docs/legal/politique-confidentialite.md (versionné Git)']
major_change boolean [not null, default: false, note: 'true = popup obligatoire pour tous les users']
changelog text [not null, note: 'Résumé des changements pour communication']
effective_date timestamp [not null, note: 'Date d entrée en vigueur de cette version']
created_at timestamp [not null, default: `now()`]
indexes {
(version) [unique]
(effective_date) [note: 'Order versions chronologically']
}
}
Table users {
id uuid [primary key]
}
Table user_policy_acceptances {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
policy_version_id uuid [not null, ref: > privacy_policy_versions.id]
accepted boolean [not null, note: 'true = accepté, false = refusé (compte gelé)']
accepted_at timestamp [not null, default: `now()`]
ip_address inet [not null, note: 'IP de l utilisateur lors de l acceptation (preuve CNIL)']
indexes {
(user_id, policy_version_id) [unique, note: 'Un user ne peut accepter qu une fois une version']
(user_id, accepted_at) [note: 'Historique acceptations user']
(policy_version_id) [note: 'Count acceptances par version']
}
}
```
## Légende
**Versioning** :
- `major_change`: `true` → popup obligatoire pour tous les utilisateurs
- `major_change`: `false` → notification simple
- Fichier source : `docs/legal/politique-confidentialite.md` (versionné Git)
**Popup si changement majeur** :
- Utilisateur doit accepter nouvelle version pour continuer
- Refus → compte gelé (lecture seule)

View File

@@ -0,0 +1,105 @@
# Reports
📖 Signalements de contenu et workflow de modération
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
username varchar(50)
}
Table contents {
id uuid [primary key]
title varchar(255)
user_id uuid [not null]
}
Table reports {
id uuid [primary key]
content_id uuid [not null, ref: > contents.id, note: 'Content being reported']
reporter_id uuid [not null, ref: > users.id, note: 'User who filed the report']
moderator_id uuid [ref: > users.id, note: 'Moderator assigned to review (NULL if pending)']
category report_category_enum [not null]
status report_status_enum [not null, default: 'pending']
comment text [note: 'Reporter explanation (mandatory for "other" category)']
evidence_url varchar(512) [note: 'Screenshot or additional proof URL']
reported_at timestamp [not null, default: `now()`]
reviewed_at timestamp [note: 'When moderator reviewed the report']
moderator_notes text [note: 'Internal moderator notes']
action_taken report_action_enum [note: 'Action decided by moderator']
indexes {
(content_id, status) [note: 'Find all reports for a content']
(status, reported_at) [note: 'Queue for moderators (pending first)']
(reporter_id) [note: 'User report history (detect abuse)']
(moderator_id) [note: 'Reports assigned to moderator']
}
}
Enum report_category_enum {
spam [note: 'Contenu publicitaire non sollicité']
hate_speech [note: 'Discours haineux, discrimination']
violence [note: 'Violence explicite']
sexual_content [note: 'Contenu sexuel inapproprié']
misinformation [note: 'Désinformation, fake news']
copyright [note: 'Violation de droits d auteur']
wrong_age_rating [note: 'Classification d âge incorrecte']
other [note: 'Autre raison (commentaire obligatoire)']
}
Enum report_status_enum {
pending [note: 'En attente de revue']
under_review [note: 'En cours d examen par modérateur']
actioned [note: 'Action prise (contenu retiré/édité)']
dismissed [note: 'Signalement rejeté (contenu valide)']
duplicate [note: 'Doublon d un signalement existant']
}
Enum report_action_enum {
content_removed [note: 'Contenu supprimé']
content_edited [note: 'Métadonnées modifiées (âge, tags)']
warning_sent [note: 'Avertissement au créateur']
strike_issued [note: 'Strike ajouté au créateur']
account_suspended [note: 'Compte créateur suspendu']
no_action [note: 'Aucune action (signalement infondé)']
}
```
## Légende
**Catégories de signalement** :
- `spam` : Contenu publicitaire non sollicité
- `hate_speech` : Discours haineux, discrimination
- `violence` : Violence explicite
- `sexual_content` : Contenu sexuel inapproprié
- `misinformation` : Désinformation, fake news
- `copyright` : Violation de droits d'auteur
- `wrong_age_rating` : Classification d'âge incorrecte
- `other` : Autre raison (commentaire obligatoire)
**Statuts** :
- `pending` : En attente de revue
- `under_review` : En cours d'examen par modérateur
- `actioned` : Action prise (contenu retiré/édité)
- `dismissed` : Signalement rejeté (contenu valide)
- `duplicate` : Doublon d'un signalement existant
**Actions possibles** :
- `content_removed` : Contenu supprimé
- `content_edited` : Métadonnées modifiées (âge, tags)
- `warning_sent` : Avertissement au créateur
- `strike_issued` : Strike ajouté au créateur
- `account_suspended` : Compte créateur suspendu
- `no_action` : Aucune action (signalement infondé)
**Workflow modération** :
- **3 premiers contenus** : Modération préalable obligatoire
- **Après validation** : Modération a posteriori (signalements)
- **Priorisation** : Nombre de signalements (>3 = urgent)
- **Délai de traitement** : <48h pour signalements critiques

View File

@@ -0,0 +1,64 @@
# Sessions
📖 Gestion des sessions utilisateur et tokens d'authentification OAuth2/OIDC
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
email varchar(255) [not null, unique]
username varchar(50) [not null, unique]
}
Table devices {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
device_name varchar(255)
device_type varchar(50)
}
Table sessions {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
device_id uuid [ref: > devices.id]
access_token_hash varchar(64) [not null, note: 'SHA256 hash, never stored in clear']
refresh_token_hash varchar(64) [not null, note: 'SHA256 hash, auto-rotated']
access_token_expires_at timestamp [not null, note: 'Lifetime: 15 minutes']
refresh_token_expires_at timestamp [not null, note: 'Lifetime: 30 days (rolling)']
ip_address inet [not null]
user_agent text [not null]
city varchar(100)
country_code char(2)
created_at timestamp [not null, default: `now()`]
last_activity_at timestamp [not null, default: `now()`]
revoked_at timestamp [note: 'NULL if active, timestamp if manually revoked']
indexes {
(user_id, revoked_at) [note: 'Find active sessions for user']
(device_id)
(refresh_token_hash) [unique, note: 'Detect replay attacks']
(last_activity_at) [note: 'Auto-cleanup inactive sessions']
}
}
```
## Légende
**Durées de vie** :
- Access token : **15 minutes**
- Refresh token : **30 jours** (rotatif)
- Inactivité : Déconnexion automatique après **30 jours**
**Sécurité** :
- Tokens stockés en **SHA256** (jamais en clair)
- Rotation automatique des refresh tokens
- Détection replay attack
**Multi-device** :
- Sessions simultanées **illimitées**
- Révocation individuelle ou globale possible
- Alertes si connexion depuis nouveau device ou pays différent

View File

@@ -0,0 +1,65 @@
# User Profile History
📖 Audit trail modifications profil (Article 16 RGPD - Droit de rectification)
## Diagramme
```kroki-dbml
Table users {
id uuid [primary key]
email varchar(255)
username varchar(50)
bio text
}
Table user_profile_history {
id uuid [primary key]
user_id uuid [not null, ref: > users.id]
field_name profile_field_enum [not null, note: 'Champ modifié (email, username, bio, etc.)']
old_value text [note: 'Valeur avant modification (NULL si création)']
new_value text [not null, note: 'Nouvelle valeur']
change_reason change_reason_enum [not null]
ip_address inet [not null, note: 'IP de l origine du changement']
changed_at timestamp [not null, default: `now()`]
indexes {
(user_id, changed_at) [note: 'Timeline modifications user (ordre chronologique)']
(field_name, changed_at) [note: 'Track modifications par type de champ']
(user_id, field_name) [note: 'Historique d un champ spécifique']
}
}
Enum profile_field_enum {
email [note: 'Re-vérification requise après changement']
username [note: 'Limite: 1 changement/30j']
bio [note: 'Biographie utilisateur']
avatar_url [note: 'URL de l avatar']
date_of_birth [note: 'Date de naissance']
}
Enum change_reason_enum {
user_edit [note: 'Modification self-service utilisateur']
admin_correction [note: 'Correction par admin (via backoffice)']
gdpr_request [note: 'Suite demande RGPD formelle (droit de rectification)']
}
```
## Légende
**Champs trackés** :
- `email`: Re-vérification requise
- `username`: Limite 1 changement/30j
- `bio`, `avatar_url`, `date_of_birth`
**Change reasons** :
- `user_edit`: Modification self-service utilisateur
- `admin_correction`: Correction par admin
- `gdpr_request`: Suite demande RGPD formelle
**Audit** :
- Historique complet conservé (preuve légale)
- Accessible utilisateur : "Historique de mes modifications"
- Accessible DPO : investigations

View File

@@ -0,0 +1,320 @@
# Modèle de données - Entités globales
📖 Entités de base utilisées dans tous les domaines métier
## Diagramme
```mermaid
erDiagram
USERS ||--o{ CONTENTS : "crée"
USERS ||--o{ SUBSCRIPTIONS : "s'abonne à"
USERS ||--o{ LISTENING_HISTORY : "écoute"
USERS ||--o{ SESSIONS : "possède"
USERS ||--o{ DEVICES : "possède"
USERS ||--o{ USER_CONSENTS : "donne"
USERS ||--o{ LOCATION_HISTORY : "génère"
USERS ||--o{ INTEREST_GAUGES : "possède"
USERS ||--o{ REPORTS : "signale"
USERS ||--o{ DATA_EXPORTS : "demande"
USERS ||--o{ PARENTAL_CONSENTS : "a"
USERS ||--o{ ACCOUNT_DELETIONS : "demande"
USERS ||--o{ USER_PROFILE_HISTORY : "modifie"
PARENTAL_CONSENTS ||--|| PARENTAL_CONTROLS : "configure"
PRIVACY_POLICY_VERSIONS ||--o{ USER_POLICY_ACCEPTANCES : "acceptée par"
USERS ||--o{ USER_POLICY_ACCEPTANCES : "accepte"
BREACH_INCIDENTS ||--o{ BREACH_AFFECTED_USERS : "impacte"
USERS ||--o{ BREACH_AFFECTED_USERS : "est impacté"
CONTENTS ||--o{ LISTENING_HISTORY : "écouté"
CONTENTS }o--|| USERS : "créé par"
CONTENTS ||--o{ REPORTS : "reçoit"
DEVICES ||--o{ SESSIONS : "a"
USERS {
uuid id PK
string email UK
string pseudo UK
date birthdate
string role
string account_status
timestamp created_at
timestamp last_login_at
boolean email_verified
boolean kyc_verified
string phone_number
int trust_score
timestamp deletion_requested_at
timestamp inactivity_notified_at
}
CONTENTS {
uuid id PK
uuid creator_id FK
string title
string audio_url
string status
string moderation_status
string age_rating
string geo_type
point geo_location
string[] tags
int duration_seconds
timestamp published_at
int reports_count
text moderation_notes
}
SUBSCRIPTIONS {
uuid id PK
uuid subscriber_id FK
uuid creator_id FK
timestamp subscribed_at
}
LISTENING_HISTORY {
uuid id PK
uuid user_id FK
uuid content_id FK
uuid creator_id FK
boolean is_subscribed
decimal completion_rate
int last_position_seconds
string source
boolean auto_like
timestamp listened_at
}
SESSIONS {
uuid id PK
uuid user_id FK
uuid device_id FK
string access_token_hash
string refresh_token_hash
timestamp access_token_expires_at
timestamp refresh_token_expires_at
inet ip_address
string user_agent
string city
string country_code
timestamp created_at
timestamp last_activity_at
timestamp revoked_at
}
DEVICES {
uuid id PK
uuid user_id FK
string device_name
string os
string browser
string device_type
boolean is_trusted
timestamp trusted_until
timestamp first_seen_at
timestamp last_seen_at
}
USER_CONSENTS {
uuid id PK
uuid user_id FK
string consent_type
string consent_version
boolean accepted
timestamp given_at
}
LOCATION_HISTORY {
uuid id PK
uuid user_id FK
geography location
string geohash
boolean anonymized
string context
timestamp created_at
timestamp anonymized_at
}
INTEREST_GAUGES {
uuid id PK
uuid user_id FK
string category
decimal score
timestamp last_updated
int interactions_count
}
REPORTS {
uuid id PK
uuid content_id FK
uuid reporter_id FK
uuid moderator_id FK
string category
string status
text comment
timestamp reported_at
timestamp reviewed_at
text moderator_notes
string action_taken
}
DATA_EXPORTS {
uuid id PK
uuid user_id FK
string status
string export_url
bigint size_bytes
string format
timestamp requested_at
timestamp generated_at
timestamp expires_at
}
PARENTAL_CONSENTS {
uuid id PK
uuid user_id FK
string parent_email
boolean validated
timestamp validated_at
timestamp revoked_at
}
PARENTAL_CONTROLS {
uuid id PK
uuid parental_consent_id FK
boolean gps_enabled
boolean messaging_enabled
boolean content_16plus_enabled
}
PRIVACY_POLICY_VERSIONS {
uuid id PK
string version
boolean major_change
timestamp effective_date
}
USER_POLICY_ACCEPTANCES {
uuid id PK
uuid user_id FK
uuid policy_version_id FK
boolean accepted
timestamp accepted_at
}
ACCOUNT_DELETIONS {
uuid id PK
uuid user_id FK
string status
timestamp requested_at
timestamp effective_at
timestamp deleted_at
}
BREACH_INCIDENTS {
uuid id PK
string severity
int estimated_users_count
timestamp detected_at
timestamp cnil_notified_at
boolean user_notification_required
}
BREACH_AFFECTED_USERS {
uuid id PK
uuid breach_id FK
uuid user_id FK
timestamp notified_at
}
USER_PROFILE_HISTORY {
uuid id PK
uuid user_id FK
string field_name
text old_value
text new_value
timestamp changed_at
}
DATA_RETENTION_LOGS {
uuid id PK
string action_type
int users_processed
int users_deleted
timestamp executed_at
}
```
## Légende
**Entités de base** :
- **USERS** : Utilisateurs plateforme
- Rôles : `listener`, `creator`, `moderator`, `admin`
- Account status : `active`, `suspended`, `grace_period`, `deleted`
- Trust score : 0-100 (anti-spam, accès fonctionnalités avancées)
- **CONTENTS** : Contenus audio
- Status : `draft`, `pending_review`, `published`, `moderated`, `deleted`
- Moderation status : `pending_review`, `approved`, `rejected`
- Geo-type : `geo_ancre` (70% geo), `geo_contextuel` (50% geo), `geo_neutre` (20% geo)
- Age rating : `all`, `13+`, `16+`, `18+`
- **SUBSCRIPTIONS** : Abonnements créateurs
- Utilisé pour filtrer recommandations et calculer engagement
- **LISTENING_HISTORY** : Historique écoutes
- Source : `recommendation`, `search`, `direct_link`, `profile`, `history`, `live_notification`, `audio_guide`
- Utilisé pour scoring recommandation et statistiques créateur
**Entités authentification & sécurité** :
- **SESSIONS** : Sessions utilisateur OAuth2/OIDC ([détails](sessions.md))
- Access token : 15 min, Refresh token : 30 jours (rotatif)
- **DEVICES** : Appareils de confiance ([détails](devices.md))
- Types : `mobile`, `tablet`, `desktop`, `car`
- Appareils de confiance : bypass 2FA pendant 30 jours
**Entités RGPD & conformité** :
- **USER_CONSENTS** : Consentements RGPD avec versioning ([détails](consents.md))
- Types : `geolocation_precise`, `analytics`, `push_notifications`
- **LOCATION_HISTORY** : Historique GPS avec anonymisation 24h ([détails](location-history.md))
- Geohash précision 5 (~5km²) après 24h
- **DATA_EXPORTS** : Exports de données utilisateur ([détails](exports.md))
- Portabilité RGPD Article 20, délai 48h max
- **PARENTAL_CONSENTS** : Consentements parentaux 13-15 ans ([détails](parental-consents.md))
- Workflow validation email parent, token expire 7j
- **PARENTAL_CONTROLS** : Paramètres contrôle parental ([détails](parental-consents.md))
- GPS, messagerie, contenus +16 configurables par parent
- **PRIVACY_POLICY_VERSIONS** : Versioning politique confidentialité ([détails](privacy-policy-versions.md))
- Popup si changement majeur, historique acceptations
- **ACCOUNT_DELETIONS** : Suppressions avec grace period 30j ([détails](account-deletions.md))
- Annulation possible, suppression effective automatique
- **BREACH_INCIDENTS** : Registre violations de données ([détails](breach-incidents.md))
- Procédure 72h CNIL, notification utilisateurs si risque élevé
- **USER_PROFILE_HISTORY** : Audit trail modifications profil ([détails](user-profile-history.md))
- Droit rectification Article 16, preuve légale
- **DATA_RETENTION_LOGS** : Logs purges automatiques ([détails](data-retention-logs.md))
- Inactivité 5 ans, notifications 90j/30j/7j
**Entités recommandation & modération** :
- **INTEREST_GAUGES** : Jauges de centres d'intérêt ([détails](interest-gauges.md))
- Score 0-100 par catégorie (automobile, travel, music, etc.)
- Algorithme : 70% géo + 30% intérêts
- **REPORTS** : Signalements de contenu ([détails](reports.md))
- Catégories : `spam`, `hate_speech`, `violence`, `sexual_content`, `misinformation`, etc.
- Workflow modération avec priorisation

View File

@@ -0,0 +1,110 @@
# language: fr
@privacy @rgpd @security
Fonctionnalité: Sécurité des données (Article 32 RGPD)
En tant que responsable de traitement
Je veux garantir la sécurité des données personnelles
Afin de prévenir les violations et fuites
# Chiffrement transport
@encryption @tls
Scénario: TLS 1.3 obligatoire pour toutes les communications
Quand un client tente de se connecter à l'API
Alors la connexion utilise TLS 1.3 uniquement
Et les protocoles TLS 1.0, 1.1, 1.2 sont refusés
Et le certificat est valide et à jour
# Chiffrement stockage
@encryption @database
Scénario: Encryption at rest pour la base de données
Étant donné que PostgreSQL stocke des données utilisateurs
Alors le chiffrement AES-256 at rest est activé
Et les backups sont également chiffrés AES-256
Et les backups sont stockés offsite (règle 3-2-1)
# Tokens JWT sécurisés
@encryption @jwt
Scénario: Rotation des clés JWT tous les 90 jours
Étant donné que l'API utilise des tokens JWT RS256
Quand 90 jours se sont écoulés depuis la dernière rotation
Alors une nouvelle paire de clés RSA est générée
Et l'ancienne clé reste valide 7 jours (overlap)
Et tous les tokens sont progressivement re-signés
# CDN avec signed URLs
@cdn @signed-urls
Scénario: URLs signées expirables pour les fichiers audio
Quand un utilisateur demande à écouter un contenu
Alors l'API génère une signed URL valide 1 heure
Et l'URL contient un token HMAC
Et après expiration, l'URL retourne 403 Forbidden
# Détection breach
@breach @monitoring
Scénario: Alerte immédiate si erreurs critiques backend
Quand une erreur critique survient dans l'API (500, crash)
Alors Sentry déclenche une alerte Discord/Slack immédiate
Et l'équipe est notifiée en temps réel
Et les logs sont consultables dans Grafana
@breach @monitoring
Scénario: Alerte si pic de requêtes anormal (potentiel DDoS)
Étant donné que le trafic habituel est ~1000 req/min
Quand le trafic atteint 10000 req/min
Alors Grafana déclenche une alerte email
Et l'équipe vérifie s'il s'agit d'une attaque
@breach @monitoring
Scénario: Alerte si accès DB non autorisé
Quand une connexion PostgreSQL provient d'une IP non whitelistée
Alors PostgreSQL bloque la connexion
Et un SMS est envoyé au fondateur
Et l'IP est loggée pour investigation
# Procédure breach 72h CNIL
@breach @procedure
Scénario: Notification CNIL si violation de données sous 72h
Étant donné qu'une violation de données est détectée
Et que des données GPS utilisateurs ont fuité
Quand l'équipe évalue la gravité
Alors le runbook "docs/rgpd/procedure-breach.md" est suivi:
| Étape | Délai | Action |
| 1 | H+0 | Détection et confinement |
| 2 | H+24 | Évaluation gravité et impact |
| 3 | H+48 | Notification CNIL si risque |
| 4 | H+72 | Notification utilisateurs si élevé|
Et un email pré-rédigé est envoyé à la CNIL
Et le formulaire en ligne CNIL est rempli
# Mesures organisationnelles
@security @access-control
Scénario: Accès DB uniquement via VPN et whitelist IP
Étant donné que je suis un développeur
Quand je tente d'accéder à la DB de production
Alors je dois être connecté au VPN OVH
Et mon IP doit être dans la whitelist
Sinon la connexion est refusée
@security @2fa
Scénario: 2FA obligatoire pour les comptes administrateurs
Étant donné que je suis un administrateur
Quand je me connecte à Zitadel admin
Alors le 2FA (TOTP) est obligatoire
Et je ne peux pas désactiver le 2FA
Et chaque connexion est loggée avec IP et timestamp
@security @logs
Scénario: Anonymisation des IP dans les logs (rotation 90j)
Quand un utilisateur fait une requête API
Alors son IP est loggée pour debug
Mais les 2 derniers octets sont masqués (ex: 192.168.x.x)
Et les logs sont automatiquement supprimés après 90 jours
# Tests de sécurité
@security @pentest
Scénario: Pentest annuel obligatoire
Étant donné que l'application est en production
Quand une année s'est écoulée depuis le dernier pentest
Alors un pentest externe est planifié
Et les vulnérabilités découvertes sont corrigées sous 30 jours
Et un rapport est produit pour audit

View File

@@ -0,0 +1,112 @@
# language: fr
@privacy @rgpd @minors @parental-consent
Fonctionnalité: Protection des mineurs et consentement parental (Article 8 RGPD)
En tant que plateforme responsable
Je veux protéger les mineurs avec consentement parental
Afin de respecter l'Article 8 RGPD (13-15 ans)
Contexte:
Étant donné que je suis sur la page d'inscription
Scénario: Blocage inscription si moins de 13 ans
Quand je saisis ma date de naissance "15/03/2014"
Et que je valide le formulaire
Alors je vois le message "RoadWave est réservé aux personnes de 13 ans et plus"
Et je vois un lien vers "RoadWave Kids"
Et mon inscription est bloquée
Scénario: Inscription directe si 16 ans ou plus
Quand je saisis ma date de naissance "10/01/2008"
Et que je valide le formulaire
Alors mon compte est créé immédiatement
Et aucun consentement parental n'est requis
Et j'ai accès à toutes les fonctionnalités
Plan du Scénario: Workflow consentement parental pour 13-15 ans
Quand je saisis ma date de naissance "<date_naissance>"
Et que je saisis l'email de mon parent "parent@example.com"
Et que je valide le formulaire
Alors un email est envoyé à "parent@example.com"
Et le lien de validation expire dans 7 jours
Et mon compte est créé mais inactif
Et je vois "En attente validation parentale"
Exemples:
| date_naissance | âge |
| 15/03/2011 | 13 |
| 20/06/2010 | 14 |
| 01/12/2009 | 15 |
Scénario: Validation du consentement parental
Étant donné que je suis un mineur de 14 ans avec compte inactif
Et que mon parent a reçu l'email de validation
Quand mon parent clique sur le lien de validation
Alors il voit une page avec:
| Section |
| Résumé données collectées |
| Paramètres contrôle parental|
| Checkbox consentement |
Et quand il coche "J'autorise mon enfant" et valide
Alors mon compte est activé
Et je reçois un email "Compte activé par ton parent"
Et les restrictions 13-15 ans sont appliquées
Scénario: Restrictions pour comptes 13-15 ans
Étant donné que je suis un utilisateur de 14 ans avec compte validé
Alors je peux écouter des contenus autorisés
Mais je NE peux PAS:
| Restriction |
| Activer le GPS précis sans accord parent |
| Utiliser la messagerie privée |
| Voir les contenus marqués +16 |
| Afficher ma ville précise sur le profil |
Scénario: Dashboard parent - Visualisation activité enfant
Étant donné que je suis un parent avec enfant de 14 ans
Quand je me connecte à "roadwave.fr/parent/[child_id]"
Alors je vois:
| Information |
| Historique d'écoute |
| Temps d'écoute hebdomadaire |
| Dernière connexion |
Et je peux:
| Action |
| Activer/désactiver GPS précis |
| Activer/désactiver messagerie |
| Révoquer le consentement |
Scénario: Révocation du consentement parental
Étant donné que je suis un parent
Et que mon enfant de 14 ans a un compte actif
Quand je clique sur "Révoquer le consentement"
Alors le compte de mon enfant est immédiatement désactivé
Et il ne peut plus se connecter
Et je reçois un email de confirmation
@roadwave-kids
Scénario: RoadWave Kids - Inscription via compte parent
Étant donné que je suis un parent avec compte RoadWave actif
Quand je vais dans "Mon compte > Ajouter un profil enfant"
Et que je saisis:
| Champ | Valeur |
| Pseudo enfant | "Emma" |
| Date naissance | "12/08/2016" |
Alors un profil Kids est créé
Et je reçois un QR code pour l'app RoadWave Kids
Et l'enfant ne peut écouter que les contenus whitelist
@roadwave-kids
Scénario: RoadWave Kids - Restrictions strictes
Étant donné que je suis connecté sur l'app RoadWave Kids
Alors je peux uniquement:
| Fonctionnalité |
| Écouter contenus présélectionnés |
| Voir ma position ville (GeoIP) |
Mais je NE peux PAS:
| Restriction |
| Activer le GPS précis |
| Créer du contenu |
| Avoir un profil public |
| Utiliser la messagerie |
| Voir du contenu UGC |

View File

@@ -0,0 +1,99 @@
# language: fr
@privacy @rgpd @transparency
Fonctionnalité: Politique de confidentialité et transparence (Articles 13-14 RGPD)
En tant qu'utilisateur
Je veux comprendre comment mes données sont utilisées
Afin de donner un consentement éclairé
Contexte:
Étant donné que l'application RoadWave est disponible
# Popup première connexion
@first-launch
Scénario: Affichage de la politique de confidentialité à l'inscription
Quand je m'inscris pour la première fois
Alors une popup s'affiche avec la politique de confidentialité
Et je dois scroller jusqu'en bas pour activer le bouton "J'accepte"
Et je dois cocher "J'ai lu et j'accepte la politique de confidentialité"
Et je ne peux pas créer de compte sans accepter
# Contenu obligatoire
@content
Scénario: Vérification du contenu de la politique de confidentialité
Quand je consulte la page "roadwave.fr/confidentialite"
Alors je vois les informations suivantes:
| Section |
| Identité responsable traitement + DPO |
| Finalités détaillées par traitement |
| Base légale (consentement/contrat/intérêt)|
| Destinataires données (CDN, Matomo, etc.) |
| Durées de conservation |
| Droits utilisateurs (accès, rectif, etc.) |
| Droit réclamation CNIL |
| Transferts hors UE (aucun) |
# Versioning
@versioning
Scénario: Versioning de la politique avec Git et DB
Étant donné que la politique de confidentialité est modifiée
Quand l'équipe commit les changements
Alors le fichier "docs/legal/politique-confidentialite.md" est versionné Git
Et une entrée est créée dans "privacy_policy_versions"
Avec les champs:
| Champ | Valeur |
| version | "2.0" |
| effective_date| "2026-03-01" |
| major_change | true |
| changelog | "Ajout tracking..." |
@versioning
Scénario: Notification utilisateurs si changement majeur
Étant donné qu'une nouvelle version majeure de la politique est publiée
Quand un utilisateur se connecte
Alors une popup s'affiche "Politique de confidentialité mise à jour"
Et il doit l'accepter à nouveau pour continuer
Et s'il refuse, son compte est gelé (accès lecture seule)
# Transparence algorithme
@algorithm-transparency
Scénario: Explication simplifiée de l'algorithme de recommandation
Quand je vais sur "roadwave.fr/comment-ca-marche"
Alors je vois une page "Comment fonctionne la recommandation ?"
Avec les explications suivantes:
| Critère | Explication |
| Distance géographique | Contenus près de vous en priorité |
| Centres d'intérêt | Jauges automatiques selon écoutes |
| Popularité | Contenus les plus écoutés |
Et je peux désactiver la personnalisation (mode anonyme)
# Contact DPO
@dpo-contact
Scénario: Accès facile au contact DPO
Quand je vais dans "Paramètres > Confidentialité et données"
Alors je vois un bouton "Contacter le DPO"
Et le lien email "dpo@roadwave.fr" est cliquable
Et je vois "Délai de réponse : 1 mois maximum"
# Profilage et décisions automatisées
@profiling
Scénario: Information sur le profilage et opposition possible
Quand je consulte la politique de confidentialité
Alors je vois une section "Profilage et décisions automatisées"
Qui explique:
| Type décision | Impact | Opposition possible |
| Recommandations | Faible | Oui (mode anonyme) |
| Modération automatique| Élevé | Oui (contestation) |
Et je peux activer le mode anonyme à tout moment
# Sous-traitants
@subprocessors
Scénario: Liste transparente des sous-traitants
Quand je consulte "roadwave.fr/confidentialite#sous-traitants"
Alors je vois la liste complète:
| Service | Finalité | Localisation | DPA |
| OVH | Hébergement | France | |
| Bunny.net | CDN audio | UE | |
| Brevo | Emails | France | |
| Mangopay | Paiements | Luxembourg | |
Et je vois "Aucun transfert hors UE"

View File

@@ -0,0 +1,77 @@
# language: fr
@privacy @rgpd @user-rights
Fonctionnalité: Exercice des droits utilisateurs RGPD
En tant qu'utilisateur
Je veux exercer mes droits RGPD facilement
Afin de contrôler mes données personnelles
Contexte:
Étant donné que je suis connecté à mon compte
# Article 16 - Droit de rectification
@rectification
Scénario: Modification du pseudo (limite 1/30j)
Quand je vais dans "Paramètres > Mon profil"
Et que je modifie mon pseudo de "Alice123" à "AliceM"
Alors le changement est immédiat
Et je ne peux plus modifier mon pseudo pendant 30 jours
Et l'historique est enregistré dans "user_profile_history"
@rectification
Scénario: Modification de l'email avec re-vérification
Quand je modifie mon email de "old@example.com" à "new@example.com"
Alors je reçois un email de vérification sur "new@example.com"
Et je dois cliquer sur le lien dans les 24h
Et mon ancien email reste actif jusqu'à validation
# Article 21 - Droit d'opposition
@opposition
Scénario: Opposition au marketing par email
Quand je vais dans "Paramètres > Notifications"
Et que je décoche "Recevoir les emails marketing"
Alors je ne reçois plus d'emails promotionnels
Mais je reçois toujours les emails transactionnels
@opposition @anonymous-mode
Scénario: Activation du mode anonyme (recommandations génériques)
Quand je vais dans "Paramètres > Confidentialité"
Et que j'active "Mode anonyme"
Alors mes jauges d'intérêt sont ignorées
Et je reçois uniquement les top contenus de ma zone géographique
Et mon historique d'écoute n'est pas utilisé pour les recommandations
Et je vois un badge "Mode anonyme actif"
# Article 18 - Droit à la limitation du traitement
@limitation
Scénario: Gel temporaire du compte
Quand je vais dans "Paramètres > Gestion du compte"
Et que je clique sur "Mettre en pause mon compte"
Alors mon compte est gelé immédiatement
Et mes contenus sont cachés (non diffusés)
Et mon profil est invisible
Mais je peux toujours me connecter en lecture seule
Et je peux réactiver à tout moment
@limitation
Scénario: Réactivation compte gelé
Étant donné que mon compte est gelé depuis 15 jours
Quand je me connecte
Et que je clique sur "Réactiver mon compte"
Alors mon compte redevient actif immédiatement
Et mes contenus sont à nouveau visibles
# Article 12 - Délai de réponse 1 mois max
@response-time
Scénario: Demande d'accès aux données (délai 48h)
Quand je demande un export de mes données
Alors je reçois un email sous 48h maximum
Avec un lien de téléchargement valide 7 jours
@response-time
Scénario: Contestation d'une décision automatisée (délai 24h)
Étant donné que mon contenu a été bloqué par la modération automatique
Quand je clique sur "Contester cette décision"
Alors un humain examine mon cas sous 24h
Et je reçois une réponse motivée
Et mon contenu est rétabli si la décision était erronée

View File

@@ -19,6 +19,7 @@
### Contexte du report
**Raisons** :
- **Coût modération** : Classification manuelle humaine très coûteuse (~2000€/mois pour 1-2 modérateurs senior full-time)
- **Risque juridique** : Accusations de biais éditorial, contentieux DSA
- **Complexité technique** : Dashboard audit, logs 3 ans, alertes déséquilibre
@@ -26,6 +27,7 @@
- **Pas essentiel MVP** : L'application fonctionne sans ce système
**Version MVP** (actuelle) :
- Tag "Politique" simple (comme "Économie", "Sport")
- Pas de classification gauche/droite
- Pas d'équilibrage imposé
@@ -36,6 +38,7 @@
### Spécifications complètes (future implémentation)
**Échelle de classification** (5 niveaux) :
- 🔴 **Extrême gauche** (anticapitalisme radical, révolution)
- 🟠 **Gauche** (écologie, social, critique capitalisme modérée)
-**Centre/Neutre** (pas de positionnement politique clair)
@@ -44,11 +47,13 @@
- 🟢 **Non politique** (enfants, musique, fiction, culture générale)
**Qui classifie** :
- ❌ Pas de classification automatique IA (outil informatif uniquement, jamais décisionnaire)
- ✅ Modérateurs senior après transcription
- ✅ Créateur peut contester via processus d'appel
**Affichage** :
- Badge politique visible : **au choix de l'utilisateur** (paramètre "Afficher orientation politique")
- Par défaut : badges masqués (UX neutre)
@@ -62,17 +67,20 @@
| **Masquer politique** | 0% gauche / 0% droite / 100% centre-neutre + non politique | Option apolitique |
**Audit et conformité DSA** :
- Rapport hebdomadaire automatique : % gauche/droite/centre diffusé par utilisateur
- Alerte si déséquilibre global plateforme (>55% d'un bord)
- Logs conservés **3 ans** (exigence Digital Services Act EU)
- Dashboard admin : visualisation répartition temps réel
**Sanctions mauvaise classification** :
- Classification volontairement incorrecte = Strike 1
- Récidive = Strike 2 (suspension 7j)
- Détection via signalements utilisateurs + audit modération
**Justification** :
- **Conformité juridique DSA** (obligation neutralité plateforme EU)
- Protection contre accusations de biais éditorial
- Transparence auditable
@@ -90,6 +98,7 @@
5. Validation juridique du processus de classification
**Chronologie estimée** :
- Phase 1 (Post-MVP+3 mois) : Validation demande utilisateurs via sondages
- Phase 2 (Post-MVP+6 mois) : Recrutement modérateurs + développement dashboard
- Phase 3 (Post-MVP+9 mois) : Tests bêta avec utilisateurs volontaires
@@ -104,12 +113,14 @@
### Contexte du report
**Raisons** :
- **Complexité technique** : Intégration Lightning Network, gestion wallets crypto
- **Réglementation** : Incertitude juridique crypto en EU (MiCA 2025)
- **Focus MVP** : Priorité sur monétisation via abonnements Premium et publicités
- **Adoption utilisateurs** : Nécessite éducation et adoption crypto préalables
**Version MVP** (actuelle) :
- Monétisation créateurs via :
- Partage revenus publicités (3€ CPM)
- 70% revenus abonnements Premium
@@ -128,6 +139,7 @@
5. Conversion EUR/BTC automatique (optionnelle)
**Avantages Lightning Network** :
- ✅ Frais quasi-nuls (<1%) vs 1.8% Mangopay
- ✅ Transactions instantanées (<1 seconde)
- ✅ Micropaiements possibles (dès 0.01€)
@@ -135,12 +147,14 @@
- ✅ Pas d'intermédiaire (peer-to-peer)
**Contraintes** :
- ❌ Adoption crypto limitée (2-5% population EU en 2026)
- ❌ Volatilité BTC (nécessite conversion EUR immédiate)
- ❌ UX complexe pour utilisateurs non-crypto
- ❌ Réglementation MiCA en évolution
**Alternatives étudiées** :
- Ko-fi / Buy Me a Coffee : simple mais frais 5%
- PayPal/Stripe : frais 2.9% + 0.30€ (non viable pour micropaiements)
- Mangopay : déjà utilisé, mais frais élevés pour petits montants
@@ -157,6 +171,7 @@
5. Demande créateurs confirmée via sondages
**Chronologie estimée** :
- Phase 1 (Post-MVP+6 mois) : Étude de marché et demande utilisateurs
- Phase 2 (Post-MVP+12 mois) : Développement intégration Lightning
- Phase 3 (Post-MVP+15 mois) : Tests bêta avec créateurs volontaires
@@ -171,6 +186,7 @@
### Contexte du report
**Raisons** :
- **Masse critique requise** : Nécessite pool suffisant d'utilisateurs simultanés (>500) pour matching rapide (<30s)
- **Infrastructure WebRTC** : Coût serveurs TURN/STUN supplémentaire (~500€/mois pour 1000 utilisateurs actifs)
- **Complexité modération** : Contenu live non enregistré = risques abus, nécessite système de confiance et signalement robuste
@@ -178,6 +194,7 @@
- **UX conducteur** : Commandes vocales avancées nécessaires pour sécurité routière
**Version MVP** (actuelle) :
- Radio live créateurs uniquement (1 vers N)
- Pas de connexion P2P entre auditeurs
- Chat textuel limité aux POIs et commentaires
@@ -220,12 +237,14 @@
- Badge "En roulette" visible sur profil (transparence)
**Avantages** :
- ✅ Sérendipité et découverte (esprit "Chatroulette audio")
- ✅ Complémentarité conducteur/piéton (récit route vs récit urbain)
- ✅ Fidèle concept RoadWave (usagers de la route connectés)
- ✅ Réutilisation infrastructure WebRTC existante (radio live)
**Contraintes** :
- ❌ Nécessite pool minimum 500 utilisateurs actifs simultanés
- ❌ Modération temps réel complexe (contenu éphémère)
- ❌ Coût infrastructure TURN/STUN significatif
@@ -233,10 +252,12 @@
- ❌ Commandes vocales avancées requises pour conducteurs
**Monétisation** :
- Gratuit avec limitation : 3 sessions/jour de 5 min
- Premium : sessions illimitées + matching prioritaire (moins d'attente)
**Aspects légaux** :
- Âge minimum : 18 ans pour accès roulette
- Charte d'utilisation spécifique (respect, pas de contenu sexuel/violent, pas de sollicitation commerciale)
- Anonymat relatif : pseudo + ville visible, pas de photo
@@ -254,12 +275,14 @@
6. Commandes vocales avancées implémentées pour conducteurs
**Chronologie estimée** :
- Phase 1 (Post-MVP+3 mois) : Validation demande utilisateurs via sondages, analyse concurrence (Clubhouse, Twitter Spaces)
- Phase 2 (Post-MVP+6 mois) : Développement matchmaking + WebRTC P2P renforcé
- Phase 3 (Post-MVP+9 mois) : Tests bêta avec 100 utilisateurs volontaires
- Phase 4 (Post-MVP+12 mois) : Déploiement progressif si KPI positifs (>70% satisfaction, <5% signalements)
**KPI de succès** :
- Temps moyen d'attente matching : <30 secondes
- Taux satisfaction post-session : >70%
- Taux signalement : <5%
@@ -275,12 +298,14 @@
### Contexte du report
**Raisons** :
- **Coût SMS** : ~0.04€/SMS en France via Brevo (400€/mois pour 10K inscriptions)
- **Complexité UX** : Étape supplémentaire à l'inscription (friction)
- **Focus MVP** : Priorité sur l'expérience utilisateur fluide
- **Modération suffisante** : Système de strikes et signalements couvre les cas d'abus initiaux
**Version MVP** (actuelle) :
- Inscription par email uniquement (via Zitadel)
- Confirmation email obligatoire
- Détection basique emails jetables (liste noire publique)
@@ -321,28 +346,33 @@
- Au-delà → signalement automatique modération
**Affichage** :
- Badge "Vérifié ✓" visible sur profil créateur
- Non obligatoire pour auditeurs simples (seulement créateurs)
- Option "Vérifier mon compte" dans paramètres
**Règles de diffusion** :
- Contenus créateurs non-vérifiés : portée limitée à 10 km pendant 30 premiers jours
- Après 30 jours sans signalement : levée restriction
- Créateurs vérifiés : aucune restriction
**Avantages** :
- ✅ Réduction spam et comptes multiples
- ✅ Amélioration confiance plateforme
- ✅ Conformité anti-fraude (KYC léger)
- ✅ Réutilisation infrastructure Brevo (emails + SMS)
**Contraintes** :
- ❌ Coût SMS : ~400€/mois pour 10K inscriptions/mois
- ❌ Friction UX (étape supplémentaire)
- ❌ Numéros virtuels (Twilio, etc.) contournent vérification
- ❌ Certains utilisateurs réticents (vie privée)
**Alternatives étudiées** :
- **Captcha reCAPTCHA v3** : efficace mais contournable, pas de coût
- **Email reputation API** : ~0.01€/vérification (kickbox.io)
- **Vérification bancaire** : trop contraignant pour MVP
@@ -359,12 +389,14 @@
5. Conformité RGPD : consentement stockage numéro mobile
**Chronologie estimée** :
- Phase 1 (Post-MVP+3 mois) : Analyse taux spam/abus, validation besoin
- Phase 2 (Post-MVP+4 mois) : Développement détection emails temporaires + API Brevo SMS
- Phase 3 (Post-MVP+5 mois) : Tests bêta avec créateurs volontaires
- Phase 4 (Post-MVP+6 mois) : Déploiement progressif selon catégorie utilisateur (créateurs en priorité)
**KPI de succès** :
- Réduction comptes spam : >50%
- Taux vérification volontaire (créateurs) : >70%
- Friction UX acceptable : taux abandon inscription <10%
@@ -379,6 +411,7 @@
### Contexte du report
**Raisons** :
- **Complexité technique** : Intégration API TTS (Text-to-Speech), OCR pour PDF scannés, parsing multi-formats
- **Coût infrastructure** : ~0.016€/1000 caractères (Google Cloud TTS) = ~1.60€ par livre moyen (100K caractères)
- **Conformité droits d'auteur** : Risque juridique si conversion de contenus protégés sans licence
@@ -386,6 +419,7 @@
- **Usage limité** : Cas d'usage minoritaire vs contenu audio créé par la communauté
**Version MVP** (actuelle) :
- Contenu audio uniquement créé par les créateurs
- Pas de conversion automatique document → audio
- Utilisateurs doivent uploader directement fichiers audio
@@ -452,12 +486,14 @@
| **Allemand** | Anna, Max | Google Cloud TTS WaveNet |
**Avantages** :
- ✅ Différenciation Premium forte (feature exclusive)
- ✅ Fidélisation utilisateurs (consommation contenu personnel)
- ✅ Réutilisation infrastructure audio existante (HLS, NGINX Cache)
- ✅ Cas d'usage trajets longs (livres, articles longs)
**Contraintes** :
- ❌ Coût TTS : ~1.60€/livre moyen (Google Cloud TTS WaveNet)
- ❌ Coût stockage : ~0.01€/GB/mois (temporaire 90 jours)
- ❌ Risque juridique : conversion contenus protégés (livres, articles premium)
@@ -502,12 +538,14 @@
5. Infrastructure existante stable (HLS, CDN, backend Go)
**Chronologie estimée** :
- Phase 1 (Post-MVP+6 mois) : Étude de marché, sondage utilisateurs Premium, validation juridique
- Phase 2 (Post-MVP+9 mois) : Développement MVP TTS (PDF texte uniquement, FR/EN)
- Phase 3 (Post-MVP+10 mois) : Tests bêta avec 100 utilisateurs Premium volontaires
- Phase 4 (Post-MVP+12 mois) : Déploiement progressif si KPI positifs + ajout formats (EPUB, OCR)
**KPI de succès** :
- Adoption feature : >30% utilisateurs Premium l'utilisent au moins 1 fois/mois
- Satisfaction : >75% note positive (4-5/5)
- Rétention Premium : augmentation >10% grâce à cette feature
@@ -525,6 +563,7 @@
| **Total** | **~650€/mois** |
**Rentabilité** :
- Revenus Premium 1000 users : 4990€/mois (4.99€/mois × 1000)
- Coût TTS : 650€/mois (13% revenus)
- Marge après TTS : 4340€/mois (87%)
@@ -539,6 +578,7 @@
### Contexte du report
**Raisons** :
- **Couverture limitée** : ~30-40% du parc automobile EU en 2026 (CarPlay/Android Auto)
- **Complexité technique** : Intégration Siri Intents (iOS) + Google Actions (Android)
- **Modération vocale** : Signalements vocaux nécessitent enregistrement + transcription audio
@@ -546,6 +586,7 @@
- **Accessibilité secondaire** : Like automatique couvre déjà engagement conducteurs
**Version MVP** (actuelle) :
- ❌ Pas de commandes vocales
- ✅ Like automatique basé sur temps d'écoute (en voiture)
- ✅ Actions manuelles disponibles seulement en mode piéton
@@ -591,6 +632,7 @@
**Implémentation technique** :
**iOS** :
- Siri Intents (framework iOS 12+)
- Clés Intent à ajouter dans `Info.plist` :
```xml
@@ -602,25 +644,30 @@
</dict>
</array>
```
- Paramètres vocaux : détection "like", "abonne", "signale"
**Android** :
- Google Actions on Google Assistant (via assistant voice queries)
- Intégration avec Android App Actions
- Paremeters: Intent extras pour passer contenu actuel
- Fallback : repérer contenu par titre + créateur
**Limitation importante** :
- ⚠️ **CarPlay/Android Auto requis** : Fonctionalité non disponible sur interface mobile
- ⚠️ **Reconnaissance vocale réseau** : Nécessite connexion data
- ⚠️ **Latence acceptable** : <2 secondes entre commande et confirmation
**UX - Feedback utilisateur** :
- Siri : "✓ J'ai ajouté ce contenu à vos favoris"
- Google Assistant : "✓ Vous êtes maintenant abonné à [Créateur]"
- Confirmation audio pour signalement : "Signalement envoyé. Catégorie : Spam"
**Signalements vocaux** :
- Enregistrement automatique de la voix (tampon 30 secondes)
- Transcription audio → texte (via Google Cloud Speech ou similaire)
- Catégorie pré-remplie selon réponse vocale ("Spam" → catégorie Spam)
@@ -638,12 +685,14 @@
5. Système de confiance utilisateur en place (éviter abus signalements)
**Chronologie estimée** :
- Phase 1 (Post-MVP+2 mois) : Validation demande utilisateurs (CarPlay/Android Auto)
- Phase 2 (Post-MVP+4 mois) : Développement Siri Intents + Google Actions
- Phase 3 (Post-MVP+6 mois) : Tests bêta avec conducteurs volontaires
- Phase 4 (Post-MVP+8 mois) : Déploiement progressif si KPI positifs
**KPI de succès** :
- Adoption commandes vocales : >30% utilisateurs CarPlay/Android Auto
- Taux erreur reconnaissance vocale : <10%
- Satisfaction utilisateurs : >75% (4-5/5)
@@ -667,6 +716,7 @@
### Contexte du report
**Raisons** :
- **Masse critique requise** : Partenariats avec organismes officiels nécessitent base utilisateurs solide (>50K MAU)
- **Complexité technique** : Intégration APIs externes, système de priorités, TTS automatisé
- **Responsabilité légale** : Diffusion alertes sécurité = engagement fort, nécessite infrastructure stable
@@ -674,6 +724,7 @@
- **ROI incertain** : Valeur ajoutée forte mais sans revenus directs (service public)
**Version MVP** (actuelle) :
- Tous contenus = créateurs classiques
- Pas de système de priorité
- Pas de comptes officiels vérifiés
@@ -696,6 +747,7 @@
| **Compte Officiel** | Validation RoadWave manuelle + contrat partenariat | 🏛️ | **Configurable (0-3)** | **Aucune** |
**Exemples comptes officiels** :
- **Gestionnaires autoroutes** : SANEF, Vinci Autoroutes, APRR, ASF
- **Services météo** : Météo France, vigilance.gouv.fr
- **Sécurité civile** : Préfectures, Plan alerte enlèvement
@@ -796,6 +848,7 @@ Podcast reprend automatiquement à position exacte
```
**Paramètres techniques** :
- **Rayon déclenchement** : 500m-2km selon vitesse (calcul dynamique)
- **Son d'alerte** : Bip distinctif (pas agressif, mais audible)
- **Durée max alerte** : 30 secondes (format court, info essentielle)
@@ -803,6 +856,7 @@ Podcast reprend automatiquement à position exacte
- **Annulation** : bouton "Ignorer" disponible pendant countdown (mais déconseillé)
**Traçabilité** :
- Log : `user_id`, `alert_id`, `action` (played / ignored), `timestamp`
- Statistiques : taux d'écoute alertes vs taux ignore (KPI efficacité)
@@ -855,12 +909,14 @@ Podcast reprend automatiquement à position exacte
```
**TTS (Text-to-Speech)** :
- **Fournisseur** : Google Cloud TTS WaveNet (voix neurale professionnelle)
- **Coût** : ~0.016€/1000 caractères
- **Voix** : "Léa" (féminine, française, ton calme mais ferme pour alertes)
- **Normalisation audio** : -14 LUFS (comme autres contenus)
**Expiration automatique** :
- Alertes météo : 12h après fin vigilance
- Obstacles autoroute : 2h après signalement (si non mis à jour)
- Alertes enlèvement : 48h ou jusqu'à résolution officielle
@@ -909,6 +965,7 @@ Podcast reprend automatiquement à position exacte
```
**Création alerte manuelle** :
- Use case : information non automatisée (événement exceptionnel)
- Champs : Texte (TTS auto), Zone (carte), Priorité (1-3), Durée vie
- Validation admin RoadWave requise (pas auto-publication)
@@ -943,6 +1000,7 @@ Podcast reprend automatiquement à position exacte
5. Tests A/B réussis sur interruption priorité 3 (acceptabilité utilisateurs)
**Chronologie estimée** :
- Phase 1 (Post-MVP+6 mois) : Développement système priorités + dashboard admin + TTS
- Phase 2 (Post-MVP+9 mois) : Premier partenariat (Météo France, API publique simple)
- Phase 3 (Post-MVP+12 mois) : Tests bêta alertes météo avec utilisateurs volontaires
@@ -950,6 +1008,7 @@ Podcast reprend automatiquement à position exacte
- Phase 5 (Post-MVP+18 mois) : Déploiement complet si KPI positifs
**KPI de succès** :
- Taux écoute alertes priorité 3 : >95% (faible taux ignore)
- Satisfaction utilisateurs : >80% jugent alertes utiles (sondage post-alerte)
- Taux faux positifs : <2% (alerte diffusée à tort ou obsolète)
@@ -968,6 +1027,7 @@ Podcast reprend automatiquement à position exacte
| **Total** | **~255€/mois** |
**ROI** :
- Pas de revenus directs (service public)
- Valeur indirecte : **différenciation produit majeure**
- Argument commercial : "RoadWave vous protège en temps réel"

View File

@@ -10,11 +10,13 @@
- ✅ Option "Appareil de confiance" (skip 2FA pour 30 jours)
**Clarification technique** :
- Zitadel utilise OAuth2/OIDC comme **protocole** (standard moderne pour mobile)
- Mais l'authentification reste 100% **email/password natif**
- **Aucun fournisseur externe** (Google, Apple, etc.) n'est intégré
**Justification** :
- Souveraineté : pas de dépendance externe
- RGPD : données 100% contrôlées
- Coût : 0€ (Zitadel intégré)
@@ -35,11 +37,13 @@
| **Email vérifié** | Toutes fonctionnalités débloquées |
**Paramètres** :
- Lien de vérification expire après **7 jours**
- Possibilité de renvoyer le lien (max 3 fois/jour)
- Rappel in-app après création du 3ème contenu
**Justification** :
- Friction minimale à l'inscription
- Anti-spam sans bloquer l'essai du produit
- Incitation naturelle à vérifier (déblocage)
@@ -47,11 +51,13 @@
#### Pour les créateurs (monétisation)
**Vérification obligatoire sous 7 jours** pour :
- Accès au programme de monétisation
- KYC et reversement des revenus (conformité Mangopay)
- Publication illimitée de contenus
**Justification** :
- **Conformité légale** : KYC obligatoire pour transferts financiers
- **Anti-fraude** : Vérification identité réelle pour paiements
- **Responsabilité** : RoadWave doit pouvoir prouver identité créateurs monétisés
@@ -61,22 +67,26 @@
### 1.3 Données requises à l'inscription
**Obligatoires** :
- ✅ Email (format validé)
- ✅ Mot de passe (voir règles ci-dessous)
- ✅ Pseudo (3-30 caractères, alphanumérique + underscore)
- ✅ Date de naissance (vérification âge minimum)
**Optionnelles** :
- ❌ Nom complet (privacy by design)
- ❌ Photo de profil (avatar par défaut généré)
- ❌ Bio (ajout ultérieur)
**Âge minimum** :
- **13 ans minimum** (conformité réglementation réseaux sociaux EU)
- Vérification à l'inscription via date de naissance
- Blocage inscription si <13 ans avec message explicite
**Justification** :
- RGPD minimal data
- Friction réduite (4 champs max)
- Protection mineurs (obligation légale)
@@ -88,22 +98,26 @@
**Décision** : Classification obligatoire des contenus
**Catégories** :
- 🟢 **Tout public** (défaut)
- 🟡 **13+** : contenu mature léger (débats, actualité sensible)
- 🟠 **16+** : contenu mature (violence verbale, sujets sensibles)
- 🔴 **18+** : contenu adulte (langage explicite, sujets réservés)
**Règles de diffusion** :
- Utilisateur 13-15 ans → contenus 🟢 🟡 (Tout public + 13+)
- Utilisateur 16-17 ans → contenus 🟢 🟡 🟠 (Tout public + 13+ + 16+)
- Utilisateur 18+ → tous contenus 🟢 🟡 🟠 🔴
**Modération** :
- Vérification obligatoire de la classification lors de la validation
- Reclassification possible par modérateurs
- Strike si classification volontairement incorrecte
**Justification** :
- Protection mineurs (obligation légale)
- Responsabilité plateforme
- Coût : champ supplémentaire + règle algo
@@ -113,17 +127,20 @@
### 1.5 Validation mot de passe
**Règles** :
- ✅ Minimum **8 caractères**
- ✅ Au moins **1 majuscule**
- ✅ Au moins **1 chiffre**
- ❌ Pas de symbole obligatoire (simplicité)
**Validation** :
- Côté client (feedback temps réel)
- Côté backend (sécurité)
- Message d'erreur explicite par règle non respectée
**Justification** :
- Standard industrie
- Bloque 95% des mots de passe faibles
- UX acceptable (pas trop restrictif)
@@ -135,16 +152,19 @@
**Décision** : Optionnel mais recommandé
**Méthodes disponibles** :
- ✅ TOTP (Time-based One-Time Password) via app (Google Authenticator, Authy)
- ✅ Email (code 6 chiffres, expire 10 min)
- ❌ SMS (coût élevé ~0.05€/SMS)
**Appareil de confiance** :
- Option "Ne plus demander sur cet appareil" → bypass 2FA pendant **30 jours**
- Révocable depuis paramètres compte
- Liste des appareils de confiance visible
**Justification** :
- Sécurité renforcée sans coût SMS
- UX : appareil de confiance évite friction quotidienne
- Zitadel natif (0€)
@@ -154,16 +174,19 @@
### 1.7 Tentatives de connexion
**Règles** :
- Maximum **5 tentatives** par période de **15 minutes**
- Blocage temporaire après 5 échecs
- Compteur reset automatique après 15 min
- Notification email si blocage (tentative suspecte)
**Déblocage** :
- Automatique après 15 min
- Ou via lien "Mot de passe oublié"
**Justification** :
- Anti brute-force
- Standard industrie (équilibre sécurité/UX)
- Zitadel natif (0€)
@@ -173,19 +196,23 @@
### 1.8 Sessions et refresh tokens
**Durée de vie** :
- **Access token** : 15 minutes
- **Refresh token** : 30 jours
**Rotation** :
- Refresh token rotatif (nouveau token à chaque refresh)
- Ancien token invalidé immédiatement
- Détection token replay attack
**Extension automatique** :
- Si app utilisée, session prolongée automatiquement
- Inactivité 30 jours → déconnexion
**Justification** :
- Sécurité (token court-vie)
- UX (pas de reconnexion fréquente)
- Standard OAuth2/OIDC
@@ -197,15 +224,18 @@
**Décision** : Sessions simultanées illimitées
**Gestion** :
- Liste des devices connectés visible (OS, navigateur, dernière connexion, IP/ville)
- Révocation individuelle possible
- Révocation globale "Déconnecter tous les appareils"
**Alertes** :
- Notification push + email si connexion depuis nouveau device
- Détection localisation suspecte (IP pays différent)
**Justification** :
- UX maximale (écoute voiture + tablette maison + web)
- Sécurité via transparence (utilisateur voit tout)
- Coût : table sessions PostgreSQL
@@ -224,13 +254,16 @@
5. Confirmation + déconnexion tous devices (sauf celui en cours)
**Notifications** :
- Email immédiat si changement mot de passe
- Push si changement depuis appareil non reconnu
**Limite** :
- Maximum **3 demandes/heure** (anti-spam)
**Justification** :
- Standard sécurité
- Pas de coût SMS
- Protection contre attaque sociale

View File

@@ -23,6 +23,7 @@
| **Contenu national** | "Aucun contenu local disponible. Voici du contenu national qui pourrait vous intéresser" |
**Justification** :
- **UX fluide** : pas de message d'erreur bloquant "Aucun contenu"
- **User ne reste jamais sans contenu**
- **Contenu national = filet de sécurité** : actualités Le Monde, podcasts génériques
@@ -44,10 +45,12 @@
```
**Si tentative "Précédent" manuellement** :
- Message : "Ce contenu n'est plus disponible"
- Retour au contenu actuel
**Justification** :
- **Sécurité routière** : pas d'interruption brutale pendant conduite
- **User informé mais pas alarmé** : message discret
- **Empêche réécoute** : contenu modéré inaccessible
@@ -56,7 +59,7 @@
### 12.3 Perte de réseau
**Buffer adaptatif** (cf. TECHNICAL.md) :
**Buffer adaptatif** (cf. [TECHNICAL.md](../../../TECHNICAL.md)) :
| Réseau | Buffer min | Buffer cible | Buffer max |
|--------|------------|--------------|------------|
@@ -67,30 +70,36 @@
**Comportement détaillé** :
**Phase 1 : Connexion instable** (latence élevée, paquets perdus)
- Aucun message immédiat
- Lecture continue sur buffer
- Si > 10s latence : toast discret "Connexion instable"
**Phase 2 : Perte totale réseau**
- Lecture continue jusqu'à épuisement buffer
- Toast : "Hors ligne, lecture sur buffer (30s restantes)"
- Compte à rebours visible
**Phase 3 : Buffer épuisé sans reconnexion**
- Pause automatique
- Overlay : "Connexion perdue. Reconnexion en cours..."
- Retry automatique toutes les 5s (max 6 tentatives = 30s)
**Phase 4 : Basculement mode offline** (après 30s échec)
- Popup : "Voulez-vous continuer avec vos contenus téléchargés ?"
- Boutons : "Réessayer" / "Mode offline"
- Si "Mode offline" → lecture contenus téléchargés
**Reconnexion réussie** :
- Reprise automatique lecture au point d'arrêt exact
- Toast : "Connexion rétablie"
**Justification** :
- **Expérience fluide zones blanches** (tunnels, campagne)
- **Buffer généreux** : absorbe fluctuations réseau mobile
- **Mode offline secours** : si coupure prolongée
@@ -113,19 +122,23 @@
| **Notifications push géo-déclenchées** | ❌ |
**Popup au lancement** :
- **Apparition** : Premier lancement après refus géolocalisation
- **Message** : "RoadWave fonctionne mieux avec la géolocalisation activée. Sans elle, seul le contenu national sera disponible."
- **Boutons** :
- "Activer" → Redirection paramètres OS
- "Continuer sans" → Mode dégradé
- **Checkbox** : "Ne plus me demander"
**Banner permanent si refus** :
- Bandeau haut écran : "Mode limité : géolocalisation désactivée. [Activer]"
- Pas intrusif mais rappel constant
- Disparaît si géolocalisation réactivée
**Justification** :
- **App reste fonctionnelle** sans GPS (pas de blocage)
- **Incitation forte** à activer (meilleure UX)
- **Respecte choix user** (RGPD : consentement libre)

View File

@@ -5,21 +5,25 @@
**Décision** : Tarteaucitron.js + PostgreSQL backend
**Implémentation web** :
- ✅ Tarteaucitron.js (opensource, self-hosted)
- ✅ Banner RGPD français, customisable
- ✅ Granularité : fonctionnel / analytique / marketing
**Implémentation backend** :
- Table `user_consents` avec versioning
- Champs : user_id, consent_type, version, accepted, timestamp
- Historique complet conservé (preuve légale)
**Consentements requis** :
- **Géolocalisation précise** : obligatoire (banner + permission OS)
- **Analytics** : optionnel (Matomo)
- **Notifications push** : optionnel (permission OS)
**Justification** :
- Opensource, 0€, conformité RGPD garantie
- Historique backend = preuve légale en cas de contrôle
- Granularité conforme recommandations CNIL
@@ -34,21 +38,15 @@
1. Données précises conservées **24h** (recommandation personnalisée)
2. Après 24h : conversion en geohash précision 5 (~5km²)
3. Coordonnées originales supprimées définitivement
**Implémentation PostGIS** :
```sql
-- Job quotidien
UPDATE location_history
SET location = ST_SetSRID(ST_GeomFromGeoHash(ST_GeoHash(location::geography, 5)), 4326)::geography,
anonymized = true
WHERE created_at < NOW() - INTERVAL '24 hours' AND anonymized = false;
```
4. Job quotidien automatique via cron
**Exceptions** :
- ✅ Historique personnel visible (liste trajets) : conservation intégrale tant que compte actif
- ❌ Analytics globales : uniquement geohash anonyme
**Justification** :
- Vraie anonymisation RGPD (CNIL compliant)
- Permet analytics agrégées (heatmaps trafic)
- PostGIS natif, 0€
@@ -59,19 +57,10 @@ WHERE created_at < NOW() - INTERVAL '24 hours' AND anonymized = false;
**Décision** : JSON + HTML + ZIP, génération asynchrone
**Contenu de l'export** :
```
export-roadwave-[user_id]-[date].zip
├── export.json # Machine-readable
├── index.html # Human-readable (stylé)
├── audio/
│ ├── content-123.opus
│ ├── content-456.opus
│ └── ...
└── README.txt # Instructions
```
**Format export** : Archive ZIP contenant JSON (machine-readable), HTML (human-readable), fichiers audio, README
**Données exportées** :
- Profil utilisateur (email, pseudo, date inscription, bio)
- Historique d'écoute (titres, dates, durées)
- Contenus créés (audio + métadonnées)
@@ -86,9 +75,11 @@ export-roadwave-[user_id]-[date].zip
4. Délai : **48h maximum** (conformité RGPD)
**Limite** :
- Maximum **1 export/mois** (anti-abus)
**Justification** :
- Conformité article 20 RGPD (portabilité)
- Double format (human + machine)
- Worker asynchrone évite timeout
@@ -107,6 +98,7 @@ export-roadwave-[user_id]-[date].zip
5. Après 30j sans annulation : suppression effective
**Suppression effective** :
- ✅ Compte utilisateur supprimé (données personnelles)
- ✅ Historique d'écoute supprimé
- ✅ GPS historique supprimé
@@ -115,11 +107,13 @@ export-roadwave-[user_id]-[date].zip
- ⚠️ Likes et abonnements supprimés (mais compteurs préservés)
**Contenus conservés anonymement** :
- Audio files (CDN)
- Métadonnées (titre, description, tags, géolocalisation)
- Statistiques d'écoute
**Justification** :
- Grace period évite suppressions impulsives
- Anonymisation contenus = intérêt légitime communauté
- Conforme RGPD si créateur = donnée supprimée
@@ -139,16 +133,19 @@ export-roadwave-[user_id]-[date].zip
| **Précis** | GPS | Tous contenus (hyperlocaux inclus) | ✅ Requis |
**Implémentation** :
- Démarrage app : GeoIP automatique (IP → ville)
- Banner in-app : "Activez la géolocalisation pour découvrir du contenu près de chez vous"
- Upgrade volontaire vers GPS
**API GeoIP** :
- IP2Location Lite (gratuit, self-hosted, voir [ADR-019](../../../adr/019-geolocalisation-ip.md))
- Update DB mensuelle automatique
- Précision ~80% au niveau ville
**Justification** :
- RGPD : pas de consentement requis pour GeoIP (pas de donnée personnelle)
- UX dégradée acceptable (contenus disponibles)
- Progressive disclosure (upgrade optionnel)
@@ -168,15 +165,18 @@ export-roadwave-[user_id]-[date].zip
| **Créateur inactif** | 5 ans sans connexion + 2 ans sans écoute | Suppression automatique |
**Notifications avant suppression** :
- Email + push : **90 jours** avant
- Email + push : **30 jours** avant
- Email + push : **7 jours** avant
- Toute connexion = reset compteur inactivité
**Contenu conservé** :
- Contenus créés par comptes supprimés (anonymisés) : conservation indéfinie
**Justification** :
- Conformité principe minimisation RGPD
- 5 ans = équilibre raisonnable (standard industrie)
- Exception créateurs actifs = intérêt légitime plateforme
@@ -196,17 +196,20 @@ export-roadwave-[user_id]-[date].zip
| `_pk_id` | Analytique | 13 mois | Matomo (IP anonyme) | ✅ Requis |
**Analytics : Matomo self-hosted** :
- Hébergé sur nos serveurs (Docker)
- IP anonymisées automatiquement (2 derniers octets)
- Pas de cookie si consentement refusé
- Alternative : Plausible (SaaS EU, 9€/mois)
**Trackers interdits** :
- ❌ Google Analytics
- ❌ Facebook Pixel
- ❌ Hotjar, Mixpanel, etc.
**Justification** :
- Souveraineté données (pas de transfert US)
- Conformité RGPD max (CNIL compatible)
- Matomo = opensource, 0€ infra
@@ -218,10 +221,12 @@ export-roadwave-[user_id]-[date].zip
**Décision** : Document Markdown versionné Git (MVP)
**Emplacement** :
- `docs/rgpd/registre-traitements.md`
- Versionné Git (historique modifications)
**Contenu obligatoire par traitement** :
- Nom et finalité du traitement
- Catégories de données collectées
- Base légale (consentement / contrat / intérêt légitime)
@@ -230,14 +235,17 @@ export-roadwave-[user_id]-[date].zip
- Transferts hors UE (aucun prévu)
**Responsable** :
- DPO / Fondateur
- Review trimestrielle obligatoire
- Update immédiate si nouveau traitement
**Migration future** :
- Si > 100K utilisateurs : interface admin PostgreSQL
**Justification** :
- Obligation RGPD Article 30
- Markdown = simple, versionné, auditable
- 0€
@@ -258,6 +266,7 @@ export-roadwave-[user_id]-[date].zip
| Authentification suspecte | Zitadel alerts | Email équipe |
**Procédure breach** :
- Runbook : `docs/rgpd/procedure-breach.md`
- Checklist 72h CNIL :
1. H+0 : Détection et confinement
@@ -266,10 +275,12 @@ export-roadwave-[user_id]-[date].zip
4. H+72 : Notification utilisateurs si risque élevé
**Contact CNIL** :
- Email pré-rédigé (template)
- Formulaire en ligne (account CNIL créé)
**Justification** :
- Obligation RGPD Article 33 (notification 72h)
- Monitoring proactif évite découverte tardive
- Sentry gratuit < 5K events/mois
@@ -281,56 +292,328 @@ export-roadwave-[user_id]-[date].zip
**Décision** : Fondateur = DPO temporaire (MVP)
**Raison légale** :
- Non obligatoire si :
- < 250 employés
- Pas de traitement à grande échelle de données sensibles
- RoadWave : données localisation = sensible MAIS échelle MVP
**Formation** :
- CNIL : formation gratuite en ligne (4h)
- Certification CNIL "Atelier RGPD" (gratuit)
**Contact** :
- Email : dpo@roadwave.fr
- Publié dans CGU et mentions légales
- Délai réponse : **1 mois** (RGPD)
**Migration future** :
- Si > 100K utilisateurs : DPO externe mutualisé (~200€/mois)
- Ou recrutement DPO interne si > 10 employés
**Justification** :
- Conforme RGPD (non obligatoire en phase MVP)
- 0€, contrôle total
- Bonne pratique : avoir un contact identifié
---
## Récapitulatif Section 13
### 13.11 Droit de rectification
| Mesure | Implémentation | Coût |
|--------|----------------|------|
| **Consentement** | Tarteaucitron.js + PostgreSQL | 0€ |
| **Anonymisation GPS** | Geohash PostGIS (24h) | 0€ |
| **Export données** | JSON+HTML+ZIP asynchrone | 0€ |
| **Suppression compte** | Grace period 30j + anonymisation | 0€ |
| **Mode dégradé** | GeoIP IP2Location + GPS optionnel | 0€ |
| **Conservation** | Purge auto 5 ans inactivité | 0€ |
| **Analytics** | Matomo self-hosted | ~5€/mois |
| **Registre traitements** | Markdown Git | 0€ |
| **Breach detection** | Sentry + Grafana + runbook | 0€ |
| **DPO** | Fondateur formé CNIL | 0€ |
**Décision** : Interface self-service + validation immédiate
**Coût total RGPD : ~5€/mois**
**Données rectifiables** :
- Email (avec re-vérification)
- Pseudo (unique, disponibilité vérifiée)
- Bio / description
- Centres d'intérêt (jauges)
- Photo de profil
**Processus** :
- Changements immédiats (sauf email)
- Email : lien vérification → validation sous 24h
- Historique modifications conservé (audit trail)
**Limitations** :
- Pseudo : max 1 changement/30j (anti-squat)
**Justification** : Conformité Article 16 RGPD, self-service 0€
---
## Points d'attention pour Gherkin
### 13.12 Droit d'opposition
- Tester consentement géolocalisation (accept/refuse → contenus différents)
- Tester anonymisation GPS après 24h (job cron)
- Tester export données (génération complète + vérification contenu)
- Tester grace period suppression (annulation possible)
- Tester mode GeoIP (ville détectée correctement)
- Tester purge automatique (5 ans inactivité)
- Tester notifications avant purge (90j/30j/7j)
**Décision** : Opt-out granulaire, effet immédiat
| Traitement | Toggle | Effet |
|------------|--------|-------|
| **Marketing email** | Paramètres | Stop emails promo |
| **Notifications push** | Paramètres | Stop push marketing |
| **Analytics** | Banner RGPD | Exclusion Matomo |
| **Recommandations personnalisées** | "Mode anonyme" | Reco génériques uniquement |
**Mode anonyme** :
- Désactive algorithme (jauges ignorées)
- Recommandations = top contenus zone géo uniquement
- Historique non utilisé
**Justification** : Conformité Article 21 RGPD
---
### 13.13 Droit à la limitation du traitement
**Décision** : "Geler mon compte" temporaire
**Effets** :
- Compte gelé, contenus cachés, profil invisible
- Connexion lecture seule OK
- Réactivation à tout moment
**Justification** : Conformité Article 18 RGPD
---
### 13.14 Politique de confidentialité
**Décision** : Page web + popup in-app + versioning Git
**Emplacement** :
- Web : `roadwave.fr/confidentialite`
- App : page dédiée paramètres
- Popup première connexion (scroll requis)
**Contenu** : Identité responsable, finalités, base légale, destinataires, durées, droits, transferts UE
**Versioning** : Git + DB `privacy_policy_versions`, popup si changement majeur
**Justification** : Conformité Articles 13-14 RGPD
---
### 13.15 Minimisation des données
**Décision** : Collecte strictement nécessaire
| Donnée | Finalité | Optionnel |
|--------|----------|-----------|
| Email | Authentification | ❌ |
| Pseudo | Identité publique | ❌ |
| GPS précis | Reco hyperlocales | ✅ (GeoIP fallback) |
| Jauges intérêt | Reco thématiques | ✅ |
| Date naissance | Vérifier âge minimum | ❌ (année seule) |
**Non collecté** : nom/prénom réels, adresse postale (sauf créateurs payés), téléphone (sauf 2FA optionnel)
**Justification** : Conformité Article 5.1.c RGPD
---
### 13.16 Sécurité des données
**Décision** : Chiffrement multi-niveaux
| Couche | Implémentation |
|--------|----------------|
| Transport | TLS 1.3 ([ADR-006](../../../adr/006-chiffrement.md)) |
| DB | PostgreSQL encryption at rest AES-256 |
| Tokens | JWT RS256 + rotation 90j |
| CDN | Signed URLs expirables |
| Backups | AES-256 + offsite |
**Mesures orga** : Whitelist IP, Vault secrets, logs anonymisés, 2FA admins, pentest annuel
**Justification** : Conformité Article 32 RGPD
---
### 13.17 Transferts hors UE
**Décision** : Hébergement 100% France/UE
| Service | Localisation | Transfert UE |
|---------|--------------|--------------|
| Hébergement | OVH France | ❌ |
| Database | OVH France | ❌ |
| CDN | Bunny.net EU | ❌ |
| Matomo | Self-hosted France | ❌ |
**Si CDN global futur** : Clauses Contractuelles Types (CCE) 2021
**Justification** : Conformité Articles 44-50 RGPD, souveraineté données
---
### 13.18 Profilage et décisions automatisées
**Décision** : Transparence + droit opposition
| Traitement | Impact | Intervention humaine | Opposition |
|------------|--------|---------------------|------------|
| Recommandations | Faible | ❌ | ✅ (mode anonyme) |
| Modération auto | Élevé | ✅ (review 24h) | ✅ (appeal) |
**Transparence** : Page "Comment fonctionne l'algo ?", explications simplifiées
**Justification** : Conformité Article 22 RGPD
---
### 13.19 Gestion des mineurs
**Décision** : 13 ans minimum + consentement parental 13-15 ans + RoadWave Kids
#### App principale (RoadWave)
**Âge minimum** : **13 ans** (alignement YouTube/TikTok)
**Processus inscription** :
1. Saisie date naissance (JJ/MM/AAAA)
2. **Si < 13 ans** : blocage + message redirection RoadWave Kids
3. **Si 13-15 ans** : workflow consentement parental
4. **Si ≥ 16 ans** : inscription directe
**Workflow consentement parental (13-15 ans)** :
1. Ado saisit email parent
2. Email automatique parent avec lien validation (expire 7j)
3. Parent clique lien → page dédiée avec résumé données collectées, paramètres contrôle parental, checkbox consentement
4. Validation parent → compte ado activé avec restrictions
**Restrictions 13-15 ans** :
- ✅ Écoute contenus autorisés
- ✅ Création contenus (modération renforcée)
- ⚠️ GPS précis : consentement parental explicite requis
- ⚠️ Messagerie privée : désactivée par défaut
- ⚠️ Contenus sensibles : filtrés (pas de contenu +16)
- ⚠️ Profil public limité (pas d'affichage ville précise)
**Contrôles parentaux** :
- Dashboard parent : `roadwave.fr/parent/[child_id]`
- Visualisation historique écoute
- Activation/désactivation GPS précis
- Activation/désactivation messagerie
- Révocation consentement à tout moment
- Notification hebdomadaire activité
**Vérification légère identité parent** :
- Email parent ≠ email ado (vérification domaine)
- Lien expiration 7 jours
- Pas de vérification identité forte (MVP)
#### RoadWave Kids (< 13 ans)
**App dédiée** : Version séparée avec contrôles renforcés
**Caractéristiques** :
- ❌ Pas de GPS précis (GeoIP ville uniquement)
- ❌ Pas de création contenu
- ❌ Pas de profil public
- ❌ Pas de messagerie
- ✅ Contenus présélectionnés (whitelist éditoriale)
- ✅ Mode lecture seule
- ✅ Contrôle parental obligatoire
**Contenus autorisés** :
- Contes audio enfants
- Guides touristiques famille
- Podcasts éducatifs labellisés
- Histoires locales patrimoine
**Workflow inscription** :
1. Création compte parent (RoadWave standard)
2. Ajout profil enfant dans dashboard parent
3. App Kids : login via QR code parent
4. Pas de compte autonome enfant
**Modération** :
- 100% contenus présélectionnés par équipe éditoriale
- Aucun UGC accessible
- Whitelist créateurs vérifiés uniquement
**Justification** :
- Conformité Article 8 RGPD (13-16 ans selon pays)
- 13 ans France = seuil légal avec consentement parental
- App Kids = protection renforcée < 13 ans
- Alignement marché (YouTube 13+, YouTube Kids)
**Roadmap** :
- **MVP** : App principale 16+ uniquement (simplicité)
- **Phase 2** : Workflow 13-15 ans + consentement parental
- **Phase 3** : RoadWave Kids (app séparée)
---
### 13.20 Sous-traitants et DPA
**Décision** : DPA systématique, audit annuel
| Service | Traitement | Localisation | DPA | Certifications |
|---------|------------|--------------|-----|----------------|
| OVH | Hébergement | France | ✅ | ISO 27001, HDS |
| Bunny.net | CDN | UE | ✅ | ISO 27001 |
| Brevo | Emailing | France | ✅ | RGPD certified |
| Mangopay | Paiements | Luxembourg | ✅ | PCI-DSS, ACPR |
**Obligations DPA** : Traitement selon instructions, confidentialité, sécurité, assistance droits, suppression fin contrat
**Gestion** : `docs/rgpd/sous-traitants.md`, review annuelle
**Justification** : Conformité Article 28 RGPD
---
### 13.21 Analyse d'impact (DPIA)
**Décision** : DPIA obligatoire (GPS + profilage grande échelle)
**Raisons** :
- Traitement grande échelle données GPS sensibles
- Profilage automatisé recommandations
- Surveillance zones publiques
**Contenu** : Description traitement, finalités, nécessité, risques (tracking, profilage, fuite), mesures atténuation (anonymisation 24h, consentement, chiffrement, mode dégradé)
**Fichier** : `docs/rgpd/dpia-geolocalisation.md`, review annuelle
**Justification** : Conformité Article 35 RGPD (critères CNIL remplis)
---
### 13.22 Délai de réponse aux demandes
**Décision** : 1 mois max, automatisation maximale
**Canaux** : Email dpo@roadwave.fr, formulaire in-app, courrier postal
| Droit | Délai cible | Automatisation |
|-------|-------------|----------------|
| Accès (export) | 48h | ✅ Worker |
| Rectification | Immédiat | ✅ Self-service |
| Suppression | Immédiat | ✅ Self-service |
| Opposition | Immédiat | ✅ Toggles |
| Limitation | Immédiat | ✅ Gel compte |
| Portabilité | 48h | ✅ Export |
| Contestation décision | 24h | ⚠️ Manuel |
**Vérification identité** : Si email vérifié = aucune vérif supplémentaire
**Justification** : Conformité Article 12 RGPD

View File

@@ -0,0 +1,56 @@
# Anonymisation automatique GPS après 24h
```mermaid
sequenceDiagram
participant User as Utilisateur
participant App as Application
participant DB as Base de données (PostGIS)
participant Cron as Job Cron Quotidien
Note over User,App: Écoute de contenu avec GPS
User->>App: Écoute contenu (GPS activé)
App->>App: Capturer position GPS précise
App->>DB: INSERT position (lat, lon, anonymized: false)
Note over DB: Position précise stockée<br/>Utilisée pour recommandations
Note over DB,Cron: Moins de 24h : position précise conservée
App->>DB: SELECT positions pour recommandations
DB->>App: Positions GPS précises (< 24h)
App->>User: Recommandations hyperlocales
Note over Cron: 24h+ plus tard
Cron->>DB: SELECT positions WHERE created_at < NOW() - 24h AND anonymized = false
DB->>Cron: Liste positions à anonymiser
loop Pour chaque position
Cron->>DB: Convertir (lat, lon) → geohash précision 5 (~5km²)
Cron->>DB: UPDATE position avec geohash
Cron->>DB: Supprimer coordonnées précises
Cron->>DB: SET anonymized = true
end
Cron->>DB: Log anonymisation (nombre positions traitées)
Note over DB: Positions anonymisées utilisées pour analytics
App->>DB: SELECT positions anonymisées (analytics globales)
DB->>App: Positions geohash uniquement
App->>App: Générer heatmap trafic (~5km² précision)
Note over User: Exception : historique personnel
User->>App: Consulter "Mon historique d'écoute"
App->>DB: SELECT historique personnel utilisateur
DB->>App: Positions précises conservées (tant que compte actif)
App->>User: Trajets détaillés
```
**Légende** :
- **< 24h** : GPS précis conservé (recommandations hyperlocales)
- **> 24h** : Conversion automatique en geohash précision 5 (~5km²)
- **Exception** : Historique personnel conservé intact tant que compte actif
- **Analytics** : Uniquement positions anonymisées (geohash)

View File

@@ -0,0 +1,45 @@
# Séquence - Authentification
## Diagramme
```mermaid
sequenceDiagram
participant U as Utilisateur
participant A as App Mobile
participant Z as Zitadel
participant API as Backend API
participant DB as PostgreSQL
U->>A: Saisie email/password
A->>Z: POST /oauth/token (email, password)
Z->>Z: Validation credentials
Z-->>A: access_token (15min) + refresh_token (30j)
A->>API: GET /api/user/profile (Bearer token)
API->>Z: Validation JWT
Z-->>API: Token valide + user_id
API->>DB: SELECT user WHERE id = ?
DB-->>API: Données utilisateur
API->>DB: INSERT session (hash tokens, IP, device)
DB-->>API: Session créée
API-->>A: Profil utilisateur
A->>U: Connexion réussie
```
## Légende
**Acteurs** :
- Zitadel : Gère l'authentification OAuth2/OIDC
- Backend API : Valide les tokens et accède aux données
**Tokens** :
- Access token : 15 min (JWT), utilisé pour chaque requête API
- Refresh token : 30 jours, permet renouvellement access token
**Sécurité** :
- Tokens stockés hashés (SHA256) en DB
- Device fingerprinting (OS, navigateur, IP)
- Notification si nouveau device

View File

@@ -0,0 +1,44 @@
# Consentement parental (13-15 ans)
```mermaid
sequenceDiagram
participant Ado as Adolescent (13-15 ans)
participant App as Application
participant DB as Base de données
participant Email as Service Email
participant Parent as Parent
Ado->>App: Inscription (date naissance 13-15 ans)
App->>Ado: Demande email parent
Ado->>App: Saisit email parent
App->>DB: Créer compte (statut: pending_parental_consent)
App->>Email: Envoyer email validation parent
Email->>Parent: Email avec lien (expire 7j)
App->>Ado: "En attente validation parentale"
Note over Parent: Parent clique lien validation
Parent->>App: Accès page consentement
App->>Parent: Afficher résumé données + contrôles
Parent->>App: Valider consentement + paramètres
App->>DB: Enregistrer consentement parental
App->>DB: Activer compte (statut: active_minor)
App->>DB: Appliquer restrictions 13-15 ans
App->>Email: Notification ado (compte activé)
Email->>Ado: Email confirmation
App->>Email: Notification parent (récapitulatif)
Email->>Parent: Email + lien dashboard parental
Ado->>App: Connexion
App->>Ado: Accès restreint (GPS/messagerie selon config parent)
```
**Légende** :
- Délai expiration lien : 7 jours
- Restrictions 13-15 ans : GPS précis, messagerie, contenus +16 (configurables par parent)
- Dashboard parent : `roadwave.fr/parent/[child_id]`

View File

@@ -0,0 +1,66 @@
# Export de données (portabilité)
```mermaid
sequenceDiagram
participant User as Utilisateur
participant App as Application
participant DB as Base de données
participant Queue as File d'attente
participant Worker as Worker Background
participant CDN as CDN (fichiers audio)
participant Storage as Stockage temporaire
participant Email as Service Email
User->>App: Demande export données
App->>DB: Vérifier dernière demande
alt Dernière demande < 30 jours
DB->>App: Demande refusée
App->>User: "Prochain export disponible dans X jours"
else Demande autorisée
App->>DB: Créer demande export (statut: pending)
App->>Queue: Ajouter job export
App->>User: "Export en cours, email sous 48h"
Queue->>Worker: Job export disponible
Note over Worker: Génération asynchrone
Worker->>DB: Récupérer profil utilisateur
Worker->>DB: Récupérer historique d'écoute
Worker->>DB: Récupérer contenus créés (métadonnées)
Worker->>DB: Récupérer centres d'intérêt
Worker->>DB: Récupérer historique consentements
Worker->>CDN: Télécharger fichiers audio utilisateur
CDN->>Worker: Fichiers audio (.opus)
Worker->>Worker: Générer export.json (machine-readable)
Worker->>Worker: Générer index.html (human-readable)
Worker->>Worker: Générer README.txt
Worker->>Worker: Créer archive ZIP
Worker->>Storage: Stocker ZIP (expire 7j)
Storage->>Worker: URL signée (expire 7j)
Worker->>DB: Mettre à jour demande (statut: completed)
Worker->>DB: Enregistrer URL + date expiration
Worker->>Email: Envoyer email avec lien
Email->>User: Email + lien téléchargement (valide 7j)
User->>Storage: Clic lien téléchargement
Storage->>User: Téléchargement ZIP
Note over Storage: Après 7 jours
Storage->>Storage: Suppression automatique ZIP
end
```
**Légende** :
- Limite : 1 export / 30 jours (anti-abus)
- Délai génération : 48h maximum (conformité RGPD Article 20)
- Expiration lien : 7 jours
- Format : ZIP contenant JSON, HTML, audio, README

View File

@@ -0,0 +1,44 @@
# Séquence - Modération de contenu
## Diagramme
```mermaid
sequenceDiagram
participant C as Créateur
participant API as Backend API
participant DB as PostgreSQL
participant Q as Queue
participant M as Modérateur
participant N as Notification
C->>API: POST /contents (3 premiers contenus)
API->>DB: INSERT content (status=pending_review)
API->>Q: Ajout file modération
API-->>C: Contenu soumis
Q->>M: Notification nouveau contenu
M->>API: GET /moderation/contents/pending
API-->>M: Liste contenus à modérer
M->>M: Écoute + vérification
alt Validation
M->>API: POST /moderation/approve/{id}
API->>DB: UPDATE status=published
API->>N: Email + push créateur
N-->>C: Contenu publié ✓
else Rejet
M->>API: POST /moderation/reject/{id} (motif)
API->>DB: UPDATE status=rejected
API->>DB: INSERT strike (si grave)
API->>N: Email créateur (motif)
N-->>C: Contenu rejeté + motif
end
```
## Légende
**Modération préalable** : 3 premiers contenus uniquement
**Après validation** : Publication directe (modération a posteriori via signalements)
**Délai** : 48h maximum
**Strike** : +1 si violation grave

View File

@@ -0,0 +1,64 @@
# Notification violation de données (breach) - Procédure 72h CNIL
```mermaid
sequenceDiagram
participant Monitoring as Monitoring (Sentry/Grafana)
participant Equipe as Équipe Technique
participant DPO as DPO
participant DB as Base de données
participant CNIL as CNIL
participant Users as Utilisateurs impactés
Note over Monitoring: H+0 - Détection
Monitoring->>Equipe: Alerte breach détecté
Equipe->>Equipe: Confinement immédiat
Equipe->>DB: Bloquer accès compromis
Equipe->>DPO: Notification breach
Note over DPO,Equipe: H+0 à H+24 - Évaluation
DPO->>DB: Investigation périmètre
DB->>DPO: Données compromises
DPO->>DB: Liste utilisateurs impactés
DB->>DPO: X utilisateurs
DPO->>DPO: Évaluation gravité
Note over DPO: - Type données (GPS, email, etc.)<br/>- Nombre utilisateurs<br/>- Risque pour droits/libertés
alt Risque pour utilisateurs
Note over DPO: H+24 à H+48 - Préparation notification CNIL
DPO->>DPO: Rédaction rapport breach
Note over DPO: - Nature violation<br/>- Catégories/nb données<br/>- Conséquences probables<br/>- Mesures prises/envisagées
DPO->>CNIL: Notification sous 72h (email + formulaire en ligne)
CNIL->>DPO: Accusé réception
alt Risque élevé pour utilisateurs
Note over DPO: H+48 à H+72 - Notification utilisateurs
DPO->>Users: Email notification breach
Note over Users: - Nature violation<br/>- Coordonnées DPO<br/>- Mesures prises<br/>- Recommandations (changer mdp, etc.)
DPO->>Users: Notification in-app
end
else Risque faible (mesures techniques suffisantes)
DPO->>DPO: Documentation interne uniquement
Note over DPO: Pas de notification CNIL requise
end
Note over DPO: Post-incident
DPO->>Equipe: Audit sécurité complet
DPO->>DB: Enregistrement incident (registre violations)
DPO->>DPO: Plan correctif
```
**Légende** :
- **H+0 à H+24** : Détection, confinement, évaluation périmètre
- **H+24 à H+48** : Évaluation gravité, préparation rapport
- **H+48 à H+72** : Notification CNIL (si risque) + utilisateurs (si risque élevé)
- Délai CNIL : **72h maximum** (Article 33 RGPD)
- Notification utilisateurs obligatoire si **risque élevé** pour droits/libertés (Article 34 RGPD)

View File

@@ -0,0 +1,40 @@
# Séquence - Refresh Token
## Diagramme
```mermaid
sequenceDiagram
participant A as App Mobile
participant Z as Zitadel
participant DB as PostgreSQL
Note over A: Access token expiré (15min)
A->>Z: POST /oauth/token (refresh_token)
Z->>DB: Vérification hash refresh_token
alt Token valide
Z->>Z: Génération nouveaux tokens
Z->>DB: Update session (nouveau hash)
Z->>DB: Invalidation ancien refresh_token
Z-->>A: Nouveaux tokens
Note over DB: Rotation complète
else Token invalide ou rejoué
Z->>DB: Révocation toutes sessions user
Z-->>A: 401 Unauthorized
Note over A: Reconnexion requise
end
```
## Légende
**Rotation** :
- Nouveau refresh_token à chaque refresh
- Ancien token invalidé immédiatement
- Prévient replay attack
**Sécurité** :
- Si ancien token réutilisé → révocation globale
- Logs sécurité + notification utilisateur

View File

@@ -0,0 +1,51 @@
# Séquence - Signalement de contenu
## Diagramme
```mermaid
sequenceDiagram
participant U as Utilisateur
participant API as Backend API
participant DB as PostgreSQL
participant M as Modérateur
participant C as Créateur
participant N as Notification
U->>API: POST /reports (content_id, category, comment)
API->>DB: INSERT report (status=pending)
API->>DB: UPDATE contents.reports_count++
alt Priorité haute (3+ reports)
API->>N: Alerte modérateurs
end
API-->>U: Signalement enregistré
M->>API: GET /moderation/reports/pending
API-->>M: Reports triés par priorité
M->>API: PUT /reports/{id} (status=under_review)
M->>M: Écoute contenu + contexte
alt Violation confirmée
M->>API: POST /moderation/action (action_taken)
API->>DB: UPDATE content.status=moderated
API->>DB: UPDATE report.status=actioned
API->>DB: INSERT strike (créateur)
API->>N: Notification créateur + signaleur
N-->>C: Contenu retiré (motif + appel)
N-->>U: Action prise
else Infondé
M->>API: PUT /reports/{id} (status=dismissed)
API->>DB: UPDATE report
API->>N: Notification signaleur
N-->>U: Signalement rejeté
end
```
## Légende
**Priorité haute** : 3+ signalements ou catégories critiques (hate_speech, violence)
**Délai** : < 24h priorité haute, < 48h normale
**Anti-abus** : > 5 dismissed → warning signaleur
**Appel** : Créateur peut contester 7j après moderation

View File

@@ -0,0 +1,52 @@
# Suppression compte avec grace period
```mermaid
sequenceDiagram
participant User as Utilisateur
participant App as Application
participant DB as Base de données
participant Email as Service Email
participant Cron as Job Cron
User->>App: Demande suppression compte
App->>User: Confirmation (êtes-vous sûr ?)
User->>App: Confirmer
App->>DB: Désactiver compte (statut: pending_deletion)
App->>DB: Cacher contenus (visible: false)
App->>DB: Révoquer sessions/tokens
App->>DB: Enregistrer date suppression effective (J+30)
App->>Email: Email confirmation + lien annulation
Email->>User: Email (lien valide 30j)
App->>User: "Compte désactivé. Suppression dans 30 jours."
Note over User,App: Grace period 30 jours
alt Utilisateur annule
User->>App: Clic lien annulation
App->>DB: Réactiver compte (statut: active)
App->>DB: Restaurer visibilité contenus
App->>Email: Email confirmation réactivation
Email->>User: "Compte réactivé"
else 30 jours sans annulation
Cron->>DB: Job quotidien (vérif comptes pending_deletion)
DB->>Cron: Liste comptes J+30 dépassé
loop Pour chaque compte
Cron->>DB: Supprimer données personnelles
Cron->>DB: Anonymiser contenus (créateur: "Utilisateur supprimé")
Cron->>DB: Supprimer historique GPS/écoute
Cron->>DB: Marquer statut: deleted
end
Cron->>Email: Email confirmation suppression effective
Email->>User: "Compte définitivement supprimé"
end
```
**Légende** :
- Grace period : 30 jours
- Pendant grace period : compte inaccessible, contenus cachés
- Après 30j : suppression définitive, contenus anonymisés conservés

View File

@@ -0,0 +1,44 @@
# Cycle de vie - Suppression de compte
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Requested: Utilisateur demande suppression
Requested --> GracePeriod: Compte désactivé, email envoyé
GracePeriod --> Cancelled: Clic lien annulation (< 30j)
GracePeriod --> PendingDeletion: Délai 30j écoulé
Cancelled --> [*]
PendingDeletion --> Deleted: Job cron suppression effective
Deleted --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Requested | `requested` | Demande initiée, validation requise |
| Grace Period | `grace_period` | 30j annulation possible, compte inaccessible |
| Cancelled | `cancelled` | Utilisateur a annulé, compte réactivé |
| Pending Deletion | `pending_deletion` | File job cron (< 24h) |
| Deleted | `deleted` | Données supprimées, contenus anonymisés |
**Grace period** : 30 jours
**Pendant grace period** :
- Compte désactivé (login impossible)
- Contenus cachés (non diffusés)
- Sessions/tokens révoqués
- Email avec token annulation (valide 30j)
**Après 30j** :
- Données personnelles supprimées
- Contenus créés anonymisés (créateur = "Utilisateur supprimé")
- Historique GPS/écoute supprimé
- Irréversible

View File

@@ -0,0 +1,44 @@
# Cycle de vie - Incident de violation de données
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Detected: Alerte monitoring
Detected --> Contained: Confinement immédiat (H+0)
Contained --> UnderInvestigation: Évaluation gravité (H+24)
UnderInvestigation --> Resolved: Risque faible (mesures suffisantes)
UnderInvestigation --> CNILNotificationRequired: Risque utilisateurs
CNILNotificationRequired --> CNILNotified: Notification CNIL (< H+72)
CNILNotified --> Resolved: Pas de risque élevé utilisateurs
CNILNotified --> UsersNotificationRequired: Risque élevé
UsersNotificationRequired --> UsersNotified: Email + push utilisateurs (< H+72)
UsersNotified --> Resolved: Post-mortem + correctifs
Resolved --> [*]
```
## Règles
| État | Valeur | Délai max |
|------|--------|-----------|
| Detected | `detected` | H+0 |
| Contained | `contained` | H+0 (immédiat) |
| Under Investigation | `under_investigation` | H+24 |
| CNIL Notification Required | `cnil_notification_required` | H+48 |
| CNIL Notified | `cnil_notified` | H+72 (Article 33 RGPD) |
| Users Notification Required | `users_notification_required` | H+48 |
| Users Notified | `users_notified` | H+72 (Article 34 RGPD) |
| Resolved | `resolved` | Post-incident |
**Sévérité** : `low` / `medium` / `high` / `critical`
**Notification CNIL** : Obligatoire si risque pour droits/libertés utilisateurs
**Notification utilisateurs** : Obligatoire si risque **élevé**
**Runbook** : `docs/rgpd/procedure-breach.md`

View File

@@ -0,0 +1,56 @@
# Cycle de vie - Compte utilisateur
## Diagramme
```mermaid
stateDiagram-v2
[*] --> PendingEmailVerification: Inscription
[*] --> PendingParentalConsent: Inscription 13-15 ans
PendingEmailVerification --> Active: Email vérifié (16+ ans)
PendingParentalConsent --> ActiveMinor: Parent valide
PendingParentalConsent --> Expired: Token expiré (7j)
Active --> Suspended: Strikes 3/4/5
Active --> GracePeriod: Demande suppression
Active --> Frozen: Gel temporaire (limitation traitement)
Active --> Deleted: Inactivité 5 ans
ActiveMinor --> Active: 16 ans atteints
ActiveMinor --> Suspended: Modération
ActiveMinor --> Deleted: Parent révoque
Frozen --> Active: Réactivation utilisateur
Suspended --> Active: Fin suspension / Appel
Suspended --> Deleted: Suspension définitive
GracePeriod --> Active: Annulation < 30j
GracePeriod --> Deleted: Après 30j
Expired --> [*]
Deleted --> [*]
```
## Règles
| État | Valeur | Durée/Condition |
|------|--------|-----------------|
| Pending Email Verification | `pending_email_verification` | Email non vérifié (expire 24h) |
| Pending Parental Consent | `pending_parental_consent` | Ado 13-15 ans, attente validation parent (expire 7j) |
| Active | `active` | Compte fonctionnel standard (16+ ans) |
| Active Minor | `active_minor` | Compte 13-15 ans avec restrictions parentales |
| Frozen | `frozen` | Gel temporaire (lecture seule), réactivable à tout moment |
| Suspended | `suspended` | Strike 3: 7j, Strike 4: 30j, Strike 5: définitif |
| Grace Period | `grace_period` | 30j avant suppression, annulable |
| Expired | `expired` | Token expiré sans validation |
| Deleted | `deleted` | Données supprimées, contenus anonymisés, irréversible |
**Restrictions Active Minor** :
- GPS précis : configurable par parent
- Messagerie privée : désactivée par défaut
- Contenus +16 : filtrés
- Transition auto vers `active` à 16 ans
**Purge inactivité** : 5 ans sans connexion (notifications 90j/30j/7j avant)

View File

@@ -0,0 +1,32 @@
# Cycle de vie - Consentement parental
## Diagramme
```mermaid
stateDiagram-v2
[*] --> PendingValidation: Ado saisit email parent
PendingValidation --> Validated: Parent clique lien (< 7j)
PendingValidation --> Expired: Délai 7j écoulé
Validated --> Revoked: Parent révoque consentement
Validated --> AutoRevoked: Ado atteint 16 ans
Expired --> [*]
Revoked --> [*]
AutoRevoked --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Pending Validation | `pending_validation` | Email envoyé parent, token valide 7j |
| Validated | `validated` | Parent a validé, restrictions 13-15 ans actives |
| Expired | `expired` | Token expiré sans validation, compte inactif |
| Revoked | `revoked` | Parent révoque, compte désactivé immédiatement |
| Auto-Revoked | `auto_revoked` | Ado atteint 16 ans, restrictions levées automatiquement |
**Délai expiration** : 7 jours
**Révocation** : Possible à tout moment via dashboard parent
**Transition automatique** : À 16 ans → compte passe en `active` standard

View File

@@ -0,0 +1,39 @@
# Cycle de vie - Contenu
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Draft: Création
Draft --> PendingReview: Soumission (3 premiers contenus)
Draft --> Published: Soumission (si > 3 validés)
PendingReview --> Published: Validation modérateur
PendingReview --> Rejected: Refus modérateur
Published --> Moderated: Signalement validé
Published --> Deleted: Suppression
Rejected --> Draft: Édition corrections
Rejected --> Deleted: Abandon
Moderated --> Published: Appel accepté
Moderated --> Deleted: Suppression définitive
Deleted --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Draft | `draft` | Brouillon non visible |
| Pending Review | `pending_review` | Modération préalable (3 premiers, < 48h) |
| Published | `published` | Diffusé et visible |
| Moderated | `moderated` | Retiré après signalement |
| Rejected | `rejected` | Refusé par modération (éditable) |
| Deleted | `deleted` | Supprimé (fichiers conservés 30j) |
**Modération** : Préalable pour 3 premiers contenus, puis a posteriori (signalements)
**Appel** : Possible 7j après moderation

View File

@@ -0,0 +1,38 @@
# Cycle de vie - Export de données
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Pending: Demande export
Pending --> Generating: Worker démarre
Generating --> Ready: Génération OK (< 48h)
Generating --> Failed: Erreur (retry 3x)
Ready --> Downloaded: Téléchargement
Ready --> Expired: Délai 7j écoulé
Downloaded --> Expired: Délai 7j écoulé
Failed --> Pending: Retry manuel
Expired --> [*]
Failed --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Pending | `pending` | File d'attente (< 5 min) |
| Generating | `generating` | Worker background actif (< 48h RGPD) |
| Ready | `ready` | Disponible, lien email valide 7j |
| Downloaded | `downloaded` | Téléchargé (reste 7j) |
| Expired | `expired` | Supprimé automatiquement |
| Failed | `failed` | Échec après retry 3x |
**Format** : ZIP (JSON + HTML + audio files)
**Limite** : 1 export/mois
**Sécurité** : URL signée, token unique 7j

View File

@@ -0,0 +1,44 @@
# Cycle de vie - Incident de violation de données
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Detected: Alerte monitoring
Detected --> Contained: Confinement immédiat (H+0)
Contained --> UnderInvestigation: Évaluation gravité (H+24)
UnderInvestigation --> Resolved: Risque faible (mesures suffisantes)
UnderInvestigation --> CNILNotificationRequired: Risque utilisateurs
CNILNotificationRequired --> CNILNotified: Notification CNIL (< H+72)
CNILNotified --> Resolved: Pas de risque élevé utilisateurs
CNILNotified --> UsersNotificationRequired: Risque élevé
UsersNotificationRequired --> UsersNotified: Email + push utilisateurs (< H+72)
UsersNotified --> Resolved: Post-mortem + correctifs
Resolved --> [*]
```
## Règles
| État | Valeur | Délai max |
|------|--------|-----------|
| Detected | `detected` | H+0 |
| Contained | `contained` | H+0 (immédiat) |
| Under Investigation | `under_investigation` | H+24 |
| CNIL Notification Required | `cnil_notification_required` | H+48 |
| CNIL Notified | `cnil_notified` | H+72 (Article 33 RGPD) |
| Users Notification Required | `users_notification_required` | H+48 |
| Users Notified | `users_notified` | H+72 (Article 34 RGPD) |
| Resolved | `resolved` | Post-incident |
**Sévérité** : `low` / `medium` / `high` / `critical`
**Notification CNIL** : Obligatoire si risque pour droits/libertés utilisateurs
**Notification utilisateurs** : Obligatoire si risque **élevé**
**Runbook** : `docs/rgpd/procedure-breach.md`

View File

@@ -0,0 +1,32 @@
# Cycle de vie - Consentement parental
## Diagramme
```mermaid
stateDiagram-v2
[*] --> PendingValidation: Ado saisit email parent
PendingValidation --> Validated: Parent clique lien (< 7j)
PendingValidation --> Expired: Délai 7j écoulé
Validated --> Revoked: Parent révoque consentement
Validated --> AutoRevoked: Ado atteint 16 ans
Expired --> [*]
Revoked --> [*]
AutoRevoked --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Pending Validation | `pending_validation` | Email envoyé parent, token valide 7j |
| Validated | `validated` | Parent a validé, restrictions 13-15 ans actives |
| Expired | `expired` | Token expiré sans validation, compte inactif |
| Revoked | `revoked` | Parent révoque, compte désactivé immédiatement |
| Auto-Revoked | `auto_revoked` | Ado atteint 16 ans, restrictions levées automatiquement |
**Délai expiration** : 7 jours
**Révocation** : Possible à tout moment via dashboard parent
**Transition automatique** : À 16 ans → compte passe en `active` standard

View File

@@ -0,0 +1,29 @@
# Cycle de vie - Session
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Active: Connexion
Active --> Active: Refresh token
Active --> Expired: Inactivité 30j
Active --> Revoked: Déconnexion manuelle
Active --> Revoked: Changement mot de passe
Active --> Revoked: Replay attack
Expired --> [*]
Revoked --> [*]
```
## Règles
| État | Condition | Description |
|------|-----------|-------------|
| Active | `revoked_at IS NULL` | Access token 15min, Refresh token 30j |
| Expired | `refresh_token_expires_at < NOW()` | Inactivité 30j |
| Revoked | `revoked_at IS NOT NULL` | Révoquée manuellement |
**Rotation** : Refresh token rotatif (nouveau à chaque refresh)
**Sécurité** : Tokens hashés SHA256, révocation globale si replay attack
**Nettoyage** : Suppression sessions expirées/révoquées > 7j/30j

View File

@@ -0,0 +1,32 @@
# Cycle de vie - Signalement
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Pending: Signalement créé
Pending --> UnderReview: Prise en charge
Pending --> Duplicate: Doublon détecté
UnderReview --> Actioned: Violation confirmée
UnderReview --> Dismissed: Infondé
Actioned --> [*]
Dismissed --> [*]
Duplicate --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Pending | `pending` | En attente (< 48h, < 24h si priorité haute) |
| Under Review | `under_review` | En cours d'examen |
| Actioned | `actioned` | Contenu retiré/modifié + strike |
| Dismissed | `dismissed` | Signalement rejeté |
| Duplicate | `duplicate` | Doublon (même contenu < 7j) |
**Priorité haute** : 3+ signalements ou catégories `hate_speech`, `violence`
**Actions** : Contenu retiré, strike, suspension selon gravité
**Anti-abus** : > 5 dismissed → warning, limite 3 signalements/jour

View File

@@ -0,0 +1,44 @@
# Cycle de vie - Suppression de compte
## Diagramme
```mermaid
stateDiagram-v2
[*] --> Requested: Utilisateur demande suppression
Requested --> GracePeriod: Compte désactivé, email envoyé
GracePeriod --> Cancelled: Clic lien annulation (< 30j)
GracePeriod --> PendingDeletion: Délai 30j écoulé
Cancelled --> [*]
PendingDeletion --> Deleted: Job cron suppression effective
Deleted --> [*]
```
## Règles
| État | Valeur | Description |
|------|--------|-------------|
| Requested | `requested` | Demande initiée, validation requise |
| Grace Period | `grace_period` | 30j annulation possible, compte inaccessible |
| Cancelled | `cancelled` | Utilisateur a annulé, compte réactivé |
| Pending Deletion | `pending_deletion` | File job cron (< 24h) |
| Deleted | `deleted` | Données supprimées, contenus anonymisés |
**Grace period** : 30 jours
**Pendant grace period** :
- Compte désactivé (login impossible)
- Contenus cachés (non diffusés)
- Sessions/tokens révoqués
- Email avec token annulation (valide 30j)
**Après 30j** :
- Données personnelles supprimées
- Contenus créés anonymisés (créateur = "Utilisateur supprimé")
- Historique GPS/écoute supprimé
- Irréversible

View File

@@ -22,6 +22,7 @@ Le domaine **Advertising** gère la diffusion de publicités audio ciblées. C'e
## Ubiquitous Language
**Termes métier du domaine** :
- **Ad Campaign** : Campagne publicitaire avec budget et durée
- **Ad Impression** : Affichage/lecture d'une publicité
- **Ad Targeting** : Critères de ciblage (geo + intérêts)

View File

@@ -1,6 +1,6 @@
# Modèle de données - Publicités
📖 Voir [Règles métier - Section 16 : Publicités](../rules/publicites.md) | [Entités globales](../../_shared/entities/modele-global.md)
📖 Voir [Règles métier - Section 16 : Publicités](../rules/publicites.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
## Diagramme

View File

@@ -63,6 +63,7 @@ if (campaign.hours.includes(userHour)) {
**Règle 2 : Ciblage "France" = Métropole + DOM**
**France entière inclut** :
- France métropolitaine (96 départements)
- Guadeloupe (971)
- Martinique (972)
@@ -71,6 +72,7 @@ if (campaign.hours.includes(userHour)) {
- Mayotte (976)
**Publicitaire peut affiner** :
- "Région Provence-Alpes-Côte d'Azur" → Métropole uniquement
- "Département 971" → Guadeloupe uniquement
- "Ville Pointe-à-Pitre" → Guadeloupe uniquement
@@ -102,6 +104,7 @@ if (campaign.hours.includes(userHour)) {
```
Restaurant à Pointe-à-Pitre
Campagne :
- Zone : Guadeloupe (département 971)
- Horaires : 12h-14h (rush déjeuner)
@@ -114,6 +117,7 @@ User Martinique à 12h30 locale → ❌ Pas diffusion (hors zone géo)
```
Assureur national
Campagne :
- Zone : France (nationale)
- Horaires : 7h-9h + 17h-19h
@@ -136,6 +140,7 @@ Filtrage pubs :
```
**Justification** :
-**UX intuitive pour publicitaires** : "7h-9h" = matin partout (pas besoin comprendre UTC)
-**Équité géographique** : pas de discrimination DOM-TOM, publicitaires locaux peuvent cibler local, campagnes nationales touchent tous Français
-**Simplicité technique** : détection fuseau automatique (GPS ou device), PostgreSQL `AT TIME ZONE` pour calculs backend
@@ -144,6 +149,7 @@ Filtrage pubs :
**Étalement budget** :
```
Exemple campagne :
- Budget : 300€
- Durée : 14 jours
- Zone : Département du Var
@@ -156,6 +162,7 @@ Calcul automatique :
```
**Mode de paiement** :
- ✅ Prépaiement obligatoire (évite impayés)
- ✅ Carte bancaire uniquement (Mangopay)
- ✅ Recharge automatique optionnelle (si budget <10%)
@@ -171,6 +178,7 @@ Calcul automatique :
4. Si refusé → email avec raison + remboursement automatique
**Contenus interdits en pub** :
- ❌ Alcool, tabac (réglementation française)
- ❌ Jeux d'argent
- ❌ Contenu politique (pendant campagnes électorales)
@@ -194,11 +202,13 @@ Calcul automatique :
| **Répartition horaire** | Graphique par heure | Optimisation horaires |
**Métriques engagement avancées** :
- **Taux complétion par tranche d'âge** : identifier audience réceptive
- **Carte de chaleur GPS** : visualiser zones forte écoute
- **Comparatif campagnes** : A/B testing créatifs publicitaires
**Export données** :
- ✅ CSV/Excel pour analyse externe
- ✅ Graphiques interactifs (Chart.js)
- ✅ Rapport PDF automatique fin de campagne
@@ -206,6 +216,7 @@ Calcul automatique :
#### Gestion budget et alertes
**Suivi temps réel** :
- Dashboard : Budget restant, % consommé, jours restants
- Projection : "À ce rythme, budget épuisé dans X jours"
- Alerte email/push si :
@@ -215,6 +226,7 @@ Calcul automatique :
- Campagne terminée (rapport final)
**Ajustements en cours** :
- ✅ Pause campagne (budget conservé)
- ✅ Prolonger campagne (recharge budget)
- ✅ Modifier ciblage horaire/géo (si <50% budget consommé)
@@ -223,12 +235,14 @@ Calcul automatique :
#### Système d'enchères (post-MVP)
**Optionnel future** :
- Enchère au CPM (coût pour 1000 impressions)
- Priorité selon prix : pub prix élevé → diffusion privilégiée
- Floor price : 2€ CPM minimum
- Évite surcharge pub : max 1 pub / 5 contenus stricte
**Justification décision MVP** :
- Tarif fixe simple : 0.05€/écoute complète
- Pas de complexité enchères immédiatement
- Scalable : passage enchères ultérieur si demande forte
@@ -240,22 +254,26 @@ Calcul automatique :
**Décision** : Paramétrable admin + respect expérience utilisateur
**Fréquence d'insertion** :
- **Défaut : 1 pub / 5 contenus** (utilisateurs gratuits)
- **Paramétrable admin** : curseur 1/3 à 1/10
- **Utilisateurs Premium** : 0 pub (modèle sans publicité)
**Règles strictes** :
- ⚠️ **Jamais d'interruption** contenu en cours
- Pub s'insère uniquement **entre deux contenus** (pendant délai 2s)
- Rotation : même pub max **3 fois/jour** par utilisateur (évite saturation)
- Limite : max **6 pubs/heure** par utilisateur (évite spam)
**Ciblage intelligent** :
- Géolocalisation prioritaire (point GPS > ville > département > région > national)
- Centres d'intérêt secondaires (tags utilisateur)
- Horaire (campagne 7h-9h → diffusion uniquement pendant plage **heure locale utilisateur**, voir section 6.1 pour détails fuseaux horaires et DOM-TOM)
**Volume audio normalisé** :
- Pub normalisée à **-14 LUFS** (standard broadcast)
- Évite effet "pub trop forte" (frustration utilisateur)
- Validation automatique via FFmpeg lors encodage
@@ -265,21 +283,25 @@ Calcul automatique :
### 6.3 Caractéristiques publicités
**Durée** :
- Minimum : **10 secondes**
- Maximum : **60 secondes**
- Recommandé : **15-30 secondes** (sweet spot engagement)
**Skippable** :
- Délai minimum obligatoire : **5 secondes** (paramétrable admin : 3-10s)
- Bouton "Passer la publicité" apparaît après délai
- Durée minimale comptabilisée pour facturation
**Facturation** :
- **Écoute complète** (>80%) : 0.05€ facturé publicitaire
- **Skip après délai min** : 0.02€ (exposition partielle)
- **Skip immédiat** (<5s) : 0€ (pas d'engagement)
**Justification modèle tarif** :
- Incitatif qualité : pub engageante = coût réduit
- Équitable : publicitaire paie pour attention réelle
- Transparent : dashboard montre écoutes complètes vs skips

View File

@@ -28,6 +28,7 @@ Le domaine **Content** gère toute la création, publication et diffusion des co
## Ubiquitous Language
**Termes métier du domaine** :
- **Audio Guide** : Contenu structuré en séquences géolocalisées
- **Guide Sequence** : Segment d'un audio-guide déclenché à un point GPS précis
- **Live Stream** : Diffusion audio en temps réel

View File

@@ -1,6 +1,6 @@
# Modèle de données - Audio-guides
📖 Voir [Règles métier - Section 06 : Audio-guides multi-séquences](../rules/audio-guides.md) | [Entités globales](../../_shared/entities/modele-global.md)
📖 Voir [Règles métier - Section 06 : Audio-guides multi-séquences](../rules/audio-guides.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
## Diagramme

View File

@@ -1,6 +1,6 @@
# Modèle de données - Radio Live
📖 Voir [Règles métier - Section 12 : Radio Live](../rules/radio-live.md) | [Entités globales](../../_shared/entities/modele-global.md)
📖 Voir [Règles métier - Section 12 : Radio Live](../rules/radio-live.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
## Diagramme

View File

@@ -14,11 +14,13 @@
| **🚌 Transport** | Variable | Auto GPS + Manuel possible | Bus touristiques, trains panoramiques |
**Détection automatique** :
- Vitesse moyenne calculée sur 30 secondes
- Suggestion mode au démarrage : "Détection : 🚗 Voiture. Est-ce correct ? [Oui] [Changer]"
- User peut forcer mode manuellement (settings)
**Justification** :
- Flexibilité maximale créateurs et utilisateurs
- Expériences optimisées par type de déplacement
- Gestion cas limites (vélo lent vs piéton rapide)
@@ -93,12 +95,14 @@
| **Zone diffusion** | ✅ | Polygon géographique |
**Wizard de création** :
- Étape 1 : Infos générales (titre, description, mode)
- Étape 2 : Ajout séquences une par une
- Étape 3 : Preview carte (trace + points)
- Étape 4 : Validation modération (3 premiers audio-guides)
**Justification** :
- Contrôle total créateur sur expérience
- Carte preview aide visualiser parcours
- Wizard guidé = réduction friction création
@@ -133,10 +137,12 @@ Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-P
```
**Fréquence pub** :
- Gratuits : 1 pub toutes les 5 séquences (paramétrable admin 1/3 à 1/10)
- Premium : 0 pub
**Justification** :
- Pub s'insère naturellement (pas d'attente utilisateur pour déclencher)
- User garde contrôle rythme visite (pause après pub)
- Monétisation effective créateurs
@@ -199,15 +205,18 @@ Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-P
```
**Navigation libre** :
- User peut sauter séquences déjà connues
- User peut revenir en arrière à tout moment
- User peut aller directement à séquence 8 (même si 4-7 non écoutées)
**Sauvegarde progression** :
- Checkmarks ✅ sur séquences écoutées >80%
- Position exacte sauvegardée dans séquence en cours
**Justification** :
- Utilisateur contrôle 100% son rythme
- Adapté musées : visitor peut voir physiquement une œuvre lointaine et vouloir écouter sa description
- Pas de frustration (liberté totale)
@@ -241,6 +250,7 @@ Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-P
8. Séquence suivante démarre immédiatement (pas de décompte)
**Pas de système "7 secondes avant" pour les audio-guides** :
- Contrairement aux contenus géolocalisés simples (voir [../../recommendation/rules/interactions-navigation.md](../../recommendation/rules/interactions-navigation.md#511-file-dattente-et-commande-suivant))
- Les séquences se déclenchent **au point GPS exact** (rayon 30m)
- Raison : expérience guidée continue, user sait qu'il suit un parcours
@@ -268,6 +278,7 @@ Séquence 1 [fin] → PAUSE → User clique → Séquence 2 [fin] → PUB AUTO-P
- Si vitesse **>10 km/h** ET user clique bouton (Suivant/Précédent) :
- Toast 3 secondes : "⚠️ Manipulation en conduite détectée. Pour votre sécurité, demandez à un passager."
- **Action quand même exécutée** (pas de blocage)
- Justification : sensibilisation sans bloquer (passager peut légitimement manipuler)
**Schéma flux** :
@@ -278,6 +289,7 @@ Point GPS 1 (30m) → Séquence 1 AUTO → User roule → Distance affichée →
```
**Justification** :
- Flexibilité maximale : GPS optimise expérience MAIS user garde contrôle
- Gestion cas limites : routes fermées, détours, embouteillages
- Sécurité : warning sensibilise sans bloquer (passager légitime)
@@ -361,12 +373,14 @@ Quand une séquence se termine et qu'il reste un point GPS suivant, l'interface
```
**Progress bar dynamique** :
- Se remplit au fur et à mesure qu'on se rapproche du point
- Calcul : `progress = 100 - (distance_actuelle / distance_initiale * 100)`
- Exemple : distance initiale 500m, distance actuelle 175m → progress = 65%
- Couleur : vert (#4CAF50) pour la partie remplie, gris (#E0E0E0) pour le reste
**Bouton "Rejouer séq."** :
- Permet de réécouter la séquence qui vient de se terminer
- User clique → séquence actuelle redémarre depuis 0:00
- Utile si distraction pendant l'écoute
@@ -414,6 +428,7 @@ if (currentSpeed > 5) {
```
**Justification** :
- Distance + ETA = info essentielle sans surcharge visuelle
- Direction (flèche) = aide se repérer sans carte complexe
- Simplicité = moins distraction conducteur
@@ -470,10 +485,12 @@ Popup 5 secondes :
| **Faire demi-tour** | Lance navigation GPS externe (Google Maps / Waze) vers point manqué |
**Si user au-delà rayon tolérance (>100m)** :
- Aucun popup (point trop loin, probablement hors itinéraire)
- User peut naviguer manuellement (bouton Suivant)
**Justification** :
- Flexibilité créateur (ajuste selon terrain, vitesse prévue)
- Gestion intelligente imprévus (détours, routes fermées)
- User pas bloqué (toujours moyen avancer)
@@ -489,6 +506,7 @@ Popup 5 secondes :
##### Comportement bouton [▶|] Suivant
**1. Premier clic (mode GPS auto actif)** :
- Désactive GPS automatique
- Passe à la séquence suivante immédiatement
- Bascule en **mode manuel**
@@ -496,12 +514,14 @@ Popup 5 secondes :
- Timer 10 secondes démarre
**2. Deuxième clic (dans les 10 secondes suivantes)** :
- Sort de l'audio-guide
- Audio-guide mis en **pause** (historique conservé)
- Retour au **flux normal** (algorithme de recommandation)
- Toast 2s : "Audio-guide en pause"
**3. Clics suivants (après 10 secondes)** :
- Passe à la séquence suivante (comportement standard mode manuel)
- Timer 10 secondes redémarre à chaque clic
@@ -584,6 +604,7 @@ function onSuivantClick() {
```
**Justification** :
- Résout le problème des embouteillages (30 min sans contenu)
- Double intention claire : désactiver GPS puis sortir
- User garde toujours le contrôle (peut reprendre audio-guide plus tard)
@@ -625,6 +646,7 @@ function onSuivantClick() {
- Pub entre séquences
**Justification** :
- Vélo : moins de contrôle qu'auto (obstacles, arrêts), nécessite tolérance
- Transport : moins de contrôle utilisateur (suit ligne fixe), rayon large compense
- Même UX globale = cohérence
@@ -677,6 +699,7 @@ Séquence 2 [fin]
| **Transport** | Attente GPS OU user clique Suivant | Attente GPS OU user clique Suivant |
**Justification** :
- Monétisation équitable créateurs (tous modes participent)
- Pub s'insère naturellement (auto-play, pas d'attente utilisateur)
- User garde contrôle : piéton clique Suivant, voiture peut skip manuel
@@ -697,10 +720,12 @@ Séquence 2 [fin]
| **Revenus pub audio-guides** | 3€ / 1000 écoutes complètes (6% CA pub) |
**Distinction contenus normaux vs audio-guides** :
- Dashboard sépare : "Revenus contenus classiques" / "Revenus audio-guides"
- Permet créateur voir performance par type
**Justification** :
- Transparence créateur (comprend revenus)
- Incite création audio-guides (nouvelle source revenus)
@@ -731,10 +756,12 @@ Séquence 2 [fin]
| **Cloud** | PostgreSQL (sync auto) | Multi-device (reprendre sur autre appareil) |
**Synchronisation** :
- Sauvegarde locale : chaque fin de séquence + chaque 30s
- Sync cloud : à la reconnexion réseau (batch)
**Justification** :
- Expérience fluide (pas de perte progression)
- Multi-device (démarrer sur iPhone, continuer sur iPad)
- Offline-first (fonctionne sans réseau)
@@ -744,6 +771,7 @@ Séquence 2 [fin]
#### 16.6.2 Interface de reprise
**Conditions popup** :
- Dernière écoute **<30 jours**
- Progression **>0%** et **<100%** (pas terminé)
@@ -776,11 +804,13 @@ Séquence 2 [fin]
| **Voir séquences** | Affiche liste complète, user choisit séquence départ |
**Expiration progression** :
- Progression conservée **30 jours**
- Après 30j : popup "Audio-guide expiré. Recommencez depuis le début ?"
- Suppression données progression (mais historique "écouté" préservé)
**Justification** :
- Contexte clair : user sait exactement où il en est
- Flexibilité : reprendre OU recommencer (choix utilisateur)
- 30 jours = raisonnable pour tourisme multi-jours ou retour ultérieur
@@ -798,10 +828,12 @@ Séquence 2 [fin]
5. User clique Reprendre → continue séquence 4
**Conflit de version** :
- Si modifications simultanées 2 appareils (rare) : **dernière modification gagne**
- Toast : "Progression mise à jour depuis votre autre appareil"
**Justification** :
- Confort utilisateur (change d'appareil librement)
- Use case réel : planning trajet sur tablette, écoute sur smartphone en voiture

View File

@@ -5,6 +5,7 @@
**Objectif** : Proposer des contenus audio au moment précis où l'utilisateur passe devant un point d'intérêt géographique, pour enrichir son trajet avec des informations contextuelles liées au paysage.
**Contrainte principale** : **Sécurité routière**
- Aucune distraction visuelle (pas de texte à lire)
- Notification sonore uniquement + icône minimale
- Validation par un seul bouton physique au volant ("Suivant")
@@ -26,6 +27,7 @@
**Méthode** : API GPS native iOS/Android
**Principe** :
- Calcul temps d'arrivée au point GPS basé sur vitesse actuelle et distance
- Notification déclenchée **7 secondes avant** d'atteindre le point
- Permet au conducteur d'avoir le temps de réagir (2s) + décompte (5s)
@@ -118,11 +120,13 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
```
**Cas particulier : vitesse nulle ou très faible** :
- Si vitesse < 5 km/h (1.4 m/s) ET distance < 50m → notification immédiate
- Exemple : user arrêté à un feu rouge à 30m du point
- Évite notification trop tardive quand user redémarre
**Fréquence de vérification** :
- GPS updates : toutes les 1 seconde (balance batterie/précision)
- Calcul ETA : à chaque update GPS
- Notification : déclenchée immédiatement quand ETA ≤ 7s
@@ -134,6 +138,7 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
**Philosophie** : **Minimalisme absolu** pour sécurité routière
**Pas de** :
- Titre texte à lire
- Description longue
- Bouton "Annuler" ou "Plus tard"
@@ -141,6 +146,7 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
- Image/cover du contenu
**Uniquement** :
- Son bref (notification)
- Icône selon tag du contenu
- Compteur chiffres (7→1)
@@ -179,12 +185,14 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
```
**Évolution du compteur** :
- Affichage pendant 7 secondes
- Compteur décrémente : 7 → 6 → 5 → 4 → 3 → 2 → 1 → disparaît
- Police grande (72pt), bold, couleur blanche
- Background semi-transparent (noir 50% opacity)
**Notification sonore** :
- **Son** : bip court (0.5s) ou "ding" doux personnalisé RoadWave
- **Volume** : suit le volume système notification (indépendant du volume media)
- **Pas de vibration** : inutile en voiture (téléphone sur support)
@@ -200,6 +208,7 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
**Décision** : Notification sonore uniquement en mode CarPlay/Android Auto
**Contexte** :
- [CarPlay Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/carplay) interdisent les overlays qui "takeover" l'écran
- [Android Auto Media Apps Guidelines](https://developer.android.com/training/cars/media) imposent des interactions minimales
- Sécurité routière maximale = pas de distraction visuelle
@@ -207,11 +216,13 @@ class GeoContentDetector(private val fusedLocationClient: FusedLocationProviderC
**Comportement en mode CarPlay/Android Auto** :
**Désactivé** :
- Icône overlay
- Compteur visuel (7...6...5...)
- Tout élément graphique supplémentaire
**Activé uniquement** :
- Notification sonore (bip ou ding)
- Bouton "Suivant" standard (déjà présent)
@@ -280,12 +291,14 @@ class NotificationManager(private val context: Context) {
6. Contenu géolocalisé démarre après 5s
**Justification** :
- ✅ Conformité maximale CarPlay/Android Auto guidelines
- ✅ Sécurité routière (pas de distraction visuelle)
- ✅ User peut toujours valider via bouton "Suivant" standard
- ✅ Apps comparables (Waze, Apple Maps) utilisent alertes sonores similaires
**Alternative envisagée** : Mini-badge sur bouton "Suivant"
- Moins invasif qu'un compteur
- Mais toujours considéré comme overlay (zone grise)
- **Décision** : Privilégier conformité maximale (sonore uniquement)
@@ -297,15 +310,18 @@ class NotificationManager(private val context: Context) {
**User appuie sur "Suivant"** :
**Étape 1 : Transition visuelle (0.3s)**
- Icône + compteur "7" disparaissent avec fade out
- Nouveau compteur "5" apparaît (plus grand, centré)
**Étape 2 : Décompte 5 secondes**
- Compteur décrémente : 5 → 4 → 3 → 2 → 1
- Contenu actuel continue de jouer **normalement** (pas de baisse volume)
- User entend le contenu en cours pendant le décompte
**Étape 3 : Fin du décompte**
- Compteur atteint "0"
- Fade out 0.3s du contenu actuel
- Fade in 0.3s du contenu géolocalisé
@@ -327,6 +343,7 @@ T+5s : Décompte atteint 0
```
**Justification** :
- Évite silence inconfortable pendant décompte
- User continue d'écouter du contenu intéressant
- Transition naturelle
@@ -353,6 +370,7 @@ T+5s : Décompte atteint 0
### 17.3 Limitation anti-spam
**Problème identifié** :
- Routes riches en points d'intérêt (parcours touristiques)
- Risque de notifier toutes les 30 secondes
- Fatigue utilisateur + distraction conducteur
@@ -362,6 +380,7 @@ T+5s : Décompte atteint 0
#### 17.3.1 Quota : 6 contenus géolocalisés par heure
**Règle** :
- Maximum **6 contenus géolocalisés** notifiés par heure
- Fenêtre glissante : calcul sur les 60 dernières minutes (pas réinitialisation à 0h)
- Si quota atteint : notifications suivantes ignorées silencieusement
@@ -380,6 +399,7 @@ T+5s : Décompte atteint 0
```
**Exception : audio-guides multi-séquences** :
- Un audio-guide avec N séquences compte comme **1 seul contenu** dans le quota
- Une fois démarré, toutes ses séquences sont jouables (pas de limite)
- Exemple : audio-guide 8 séquences = 1 quota, contenus simples restants = 5
@@ -422,12 +442,14 @@ func RecordNotification(userID string, contentID string) {
#### 17.3.2 Cooldown : 10 minutes après notification ignorée
**Règle** :
- Si user ne clique pas sur "Suivant" pendant les 7 secondes
- → Cooldown de **10 minutes** activé
- → Aucune nouvelle notification pendant ce délai
- → Même si quota non atteint
**Justification** :
- User a probablement une raison de ne pas vouloir de contenu géolocalisé maintenant
- Évite harcèlement (notifications répétées ignorées)
- Respecte choix implicite de l'utilisateur
@@ -458,6 +480,7 @@ func ActivateCooldown(userID string) {
```
**Exception : notification validée (user a cliqué)** :
- Pas de cooldown si user a cliqué sur "Suivant"
- Même si user skip le contenu ensuite (pendant le décompte ou après)
- Cooldown = pénalité uniquement pour notification complètement ignorée
@@ -503,6 +526,7 @@ Timeline :
6. User appuie "Précédent" → **retour au contenu géolocalisé à 42s**
**Règle** : Comme décrit dans [../../recommendation/rules/interactions-navigation.md](../../recommendation/rules/interactions-navigation.md#52-commande-précédent) :
- Si temps écouté ≥ 10 secondes → replay contenu actuel depuis début
- Si temps écouté < 10 secondes → retour contenu précédent (position exacte)
@@ -567,6 +591,7 @@ if (avgSpeedKmh < 5) {
```
**Hysteresis (éviter basculements intempestifs)** :
- Nouvelle vitesse doit être stable pendant **10 secondes** avant basculement
- Exemple : user passe de 20 km/h à 3 km/h (arrêt feu rouge)
- Si vitesse remonte à 20 km/h après 8s → pas de basculement
@@ -595,6 +620,7 @@ if (avgSpeedKmh < 5) {
| **Type contenu** | Audio-guides uniquement | Tous contenus géolocalisés |
**Transition fluide** :
- Pas de popup ou message à l'utilisateur
- Basculement invisible et automatique
- Permissions ajustées automatiquement (si déjà accordées)
@@ -608,6 +634,7 @@ if (avgSpeedKmh < 5) {
**Scénario** : Autoroute A6, vitesse 130 km/h, contenu géolocalisé détecté.
**Calcul** :
- Vitesse : 130 km/h = 36.1 m/s
- ETA 7s → distance notification : 7 × 36.1 = **252 mètres** avant le point
- User a 7s pour cliquer "Suivant"
@@ -618,6 +645,7 @@ if (avgSpeedKmh < 5) {
**Conclusion** : Le système fonctionne même à très haute vitesse ✅
**Cas extrême : 180 km/h** (illégal mais théoriquement possible) :
- Vitesse : 180 km/h = 50 m/s
- ETA 7s → distance notification : 350m avant le point
- Décompte 5s : user parcourt 250m
@@ -655,6 +683,7 @@ T+57s : User clique "Suivant" → contenu Château B démarre
**Solution proposée** : **Ajuster le cooldown selon validation précédente**
Nouvelle règle :
- Notification validée (user a cliqué) : pas de cooldown
- Notification ignorée (user n'a pas cliqué) : cooldown 10 min
- Exception : si 2+ notifications validées consécutives, cooldown réduit à 5 min
@@ -687,18 +716,21 @@ func CalculateCooldown(userID string) time.Duration {
**Scénario** : User se gare à 30m d'un château, sort de voiture, visite 1h, revient.
**Problème** :
- Vitesse < 5 km/h + distance < 50m → notification immédiate
- Mais user en mode "stationnement", pas en mode "conduite"
**Solution** : **Détection mode stationnement**
Règle :
- Si vitesse < 1 km/h pendant **2 minutes** consécutives
- → Mode "stationnement" activé
- → Pas de notification de contenus géolocalisés
- → Basculement automatique en mode piéton (push arrière-plan)
**Reprise conduite** :
- Vitesse > 5 km/h pendant 10s
- → Mode "voiture" réactivé
- → Notifications reprennent (si quota non atteint)

View File

@@ -5,6 +5,7 @@
**Décision** : Formats universels avec encodage asynchrone
**Formats acceptés** :
- ✅ MP3 (`.mp3`)
- ✅ AAC (`.aac`, `.m4a`)
- ❌ WAV, FLAC (trop lourds, inutiles en voiture)
@@ -31,6 +32,7 @@
```
**Temps d'encodage estimé** :
- Contenu 5 min → ~30 secondes
- Podcast 1h → ~5 minutes
- Podcast 4h → ~20 minutes
@@ -54,11 +56,13 @@
| 2.0x | Survol rapide (modérateurs) |
**Disponible pour** :
- ✅ Modérateurs (validation rapide : 30s → 15s à 2x)
- ✅ Auditeurs (tous les contenus)
- ✅ Standard industrie (YouTube, Spotify, Apple Podcasts)
**Justification** :
- **Simplicité** : 2 formats couvrent 95% des cas d'usage
- **Coût optimisé** : pas de conversion WAV/FLAC lourds
- **Stockage réduit** : suppression original après encodage
@@ -84,6 +88,7 @@
**Zone de diffusion (obligatoire)** :
Options mutuellement exclusives :
- **Point GPS** : latitude + longitude + rayon (100m à 10km)
- **Ville** : sélection dans référentiel INSEE
- **Département** : sélection liste
@@ -91,6 +96,7 @@ Options mutuellement exclusives :
- **National** : France entière
**Tags disponibles** (1 à 3 obligatoires) :
- Automobile
- Voyage
- Famille
@@ -105,12 +111,14 @@ Options mutuellement exclusives :
- Santé
**Champs optionnels** :
- ❌ Description (ajout ultérieur)
- ❌ Image couverture (génération auto)
**Image de couverture par défaut** :
Génération automatique selon règles :
- Icône selon type géo : 📍 Ancré / 🌍 Contextuel / 🎧 Neutre
- Couleur selon tag principal : bleu (Auto), vert (Voyage), rouge (Musique), etc.
- Format 800×800px, PNG
@@ -127,6 +135,7 @@ Classification : Tout public
```
**Justification** :
- **Friction minimale** : 5 champs max = 2 min de publication
- **Publication rapide** : pas de blocage sur description/image
- **Coût 0** : pas de génération IA au MVP
@@ -161,6 +170,7 @@ Classification : Tout public
| **Zone diffusion** | Cohérente (pas "Tour Eiffel" avec zone "National") |
**Délai de validation** :
- Objectif : **24-48h** (jours ouvrés)
- Priorité : FIFO (First In First Out)
- Weekend : délai peut atteindre 72h
@@ -169,11 +179,13 @@ Classification : Tout public
**Notification créateur** :
**Si accepté** :
- Email + push : "✅ Votre contenu '[Titre]' est en ligne !"
- Lien direct vers le contenu
- Compteur : "2/3 contenus validés pour devenir créateur vérifié"
**Si refusé** :
- Email + push : "❌ Contenu '[Titre]' refusé"
- Raison détaillée : "Qualité audio insuffisante" / "Tags non pertinents" / "Classification incorrecte" / etc.
- Lien vers règles de publication
@@ -182,11 +194,13 @@ Classification : Tout public
**Après 3 validations** :
Créateur obtient **statut "Vérifié"** :
- Badge ✓ visible sur profil
- Contenus futurs publiés **immédiatement** (modération a posteriori uniquement)
- Modération seulement si signalé par utilisateurs
**Outils modérateur** :
- Écoute accélérée (1.5x ou 2x) = double productivité
- Interface dédiée : queue de contenus à valider
- Raccourcis clavier : A (Accepter), R (Rejeter), Espace (Pause)
@@ -197,6 +211,7 @@ Créateur obtient **statut "Vérifié"** :
⚠️ **Non implémenté au MVP** (complexité juridique)
Vision future (envisageable) :
- Créateurs établis peuvent opt-in "Modérateur communautaire"
- Formation obligatoire (30 min) + quiz (80%)
- Pré-validation uniquement (validation finale toujours par équipe RoadWave)
@@ -204,6 +219,7 @@ Vision future (envisageable) :
- Attribution aléatoire (pas de collusion)
**Justification décision MVP** :
- **Responsabilité juridique** : plateforme reste responsable (DSA EU)
- **Qualité garantie** : modérateurs formés et mandatés
- **Anti-spam efficace** : bloque 95% des abus dès le début
@@ -233,18 +249,22 @@ Vision future (envisageable) :
**Raisons restrictions** :
**Audio non modifiable** :
- Évite fraude : uploader contenu validé → remplacer par spam
- Intégrité : auditeurs doivent écouter ce qui a été validé
**Zone/Type non modifiables** :
- Évite manipulation : créer "Local Paris" → changer en "National" pour boost visibilité
- Évite abus : créer "Neutre" (faible pondération géo) → changer en "Ancré" (forte pondération)
**Classification non modifiable** :
- Évite contournement : uploader "Tout public" → passer en "18+" sans revalidation
- Sécurité : garantit que classification a été vérifiée
**Si besoin de changer audio/zone/classification** :
- Action : **Supprimer contenu + republier**
- Si créateur <3 contenus validés : retourne en file validation
- Si créateur ≥3 contenus validés : publication immédiate
@@ -269,11 +289,13 @@ Créateur supprime podcast écouté par 1000 personnes
```
**Notifications suppression** :
- Pas de notification aux auditeurs (pour éviter effet Streisand)
- Historique reste consultable : "Vous avez écouté ce contenu le [date]"
- Si auditeur tente de réécouter : "Ce contenu n'est plus disponible"
**Justification** :
- **Simplicité** : règles claires et non-ambiguës
- **Sécurité** : évite manipulations algorithme et contournements modération
- **Contrôle créateur** : liberté totale de supprimer (RGPD)

View File

@@ -60,6 +60,7 @@
4. Mention titre + artiste dans métadonnées (recommandé)
**Justification juridique** :
- Directive UE 2019/790 : exception citation à des fins de critique
- Jurisprudence FR : citation courte autorisée si justifiée
- 30s = standard industrie (YouTube, TikTok)
@@ -108,6 +109,7 @@ Validation normale continue
- ✅ Modération **a posteriori uniquement** (si signalé)
**Justification** :
- **Coût 0€** : réutilise écoute 30s déjà effectuée
- **Scalable** : pas de validation pour créateurs établis
- **Pragmatique** : détecte violations évidentes (90% des cas)
@@ -175,6 +177,7 @@ Validation normale continue
**Détail sanctions** :
**Avertissement (1ère fois)** :
- Suppression contenu immédiate
- Email + push + in-app : "⚠️ Contenu retiré pour violation droits d'auteur"
- Explication pédagogique : règles musique, lien vers CGU
@@ -182,22 +185,26 @@ Validation normale continue
- Créateur peut republier version corrigée
**Strike 1 (2e fois)** :
- Suppression contenu
- Strike ajouté au compteur (visible profil créateur)
- Suspension upload **3 jours**
- Email détaillé : titre détecté, timestamp, règle violée
**Strike 2 (3e fois)** :
- Idem Strike 1
- Suspension upload **7 jours**
- Warning : "Strike 2/4 - Vous approchez du seuil critique"
**Strike 3 (4e fois)** :
- Idem Strike 2
- Suspension upload **30 jours**
- Warning : "Strike 3/4 - Prochaine violation = ban définitif"
**Strike 4 - Ban définitif (5e fois)** :
- Désactivation compte créateur
- Tous contenus dépubliés
- Pas de création nouveau compte (email/téléphone blacklisté)
@@ -226,6 +233,7 @@ Créateur a Strike 2 (7 jours de suspension)
```
**Justification** :
- **Tolérance 1ère fois** : évite punir erreurs honnêtes
- **Escalade progressive** : dissuasion sans brutalité
- **4 strikes avant ban** : cohérent avec système global (sections 9, 14)
@@ -239,6 +247,7 @@ Créateur a Strike 2 (7 jours de suspension)
**Décision** : Réutilise système existant section 14.3.3
**Accès** :
- Bouton "Contester cette décision" dans notification sanction
- Délai : **7 jours** après notification
@@ -266,11 +275,13 @@ Champs standards (voir section 14) **+** champs spécifiques :
**Cas particulier : Musique libre mal détectée**
Si créateur prouve musique = licence Epidemic Sound / Artlist :
- ✅ Appel automatiquement accepté
- ✅ Ajout titre à **whitelist interne** (évite futures erreurs)
- ✅ Excuse + compensation (ex: 1 mois Premium offert)
**Justification** :
- Réutilise processus éprouvé (section 14)
- Preuve licence = résout 90% des cas
- Délai 72h acceptable (pas de suspension immédiate sur appel)
@@ -309,6 +320,7 @@ Interdit :
```
**Justification** :
- Prévention > sanction (économie modération)
- Créateurs informés = moins de violations
- Coût : 0€ (documentation statique)
@@ -382,12 +394,14 @@ Modérateur humain vérifie :
**Coût total MVP** : **0€** (validation manuelle intégrée)
**Conformité juridique** :
- ✅ Directive UE 2019/790 (droit d'auteur + exception citation)
- ✅ DSA (Digital Services Act) : modération réactive + droit d'appel
- ✅ SACEM/SDRM : protection ayants droit + processus contentieux
- ✅ Fair use : tolérance 30s conforme jurisprudence FR/UE
**Scalabilité** :
- 0-1000 contenus/mois : validation manuelle suffisante (3h/mois modération)
- 1000-10K contenus/mois : fingerprinting open-source requis
- 10K+ contenus/mois : API commerciale à considérer (ACRCloud)
@@ -404,6 +418,7 @@ Modérateur humain vérifie :
---
**Lien avec autres sections** :
- Section 4.3 : Validation des 3 premiers contenus (workflow intégré)
- Section 7.2 : Interdictions lives + fingerprinting post-MVP
- Section 14 : Système modération (signalements, sanctions, appels)

View File

@@ -18,12 +18,14 @@
4. Après 15s → **Live public**, auditeurs peuvent rejoindre
**Notification abonnés** :
-**Push notification immédiate** à tous les abonnés dans la zone géographique
- Message : "🔴 [Nom créateur] est en direct : [Titre live]"
- Tap notification → ouverture app + lecture live immédiate
- **Filtrage géographique** : si abonné hors zone, pas de notif (évite frustration)
**Limite de durée** :
- **Maximum 8 heures** par session live
- Warning créateur à 7h30 : "Votre live se terminera dans 30 min"
- Si besoin continuer → arrêt + redémarrage nouveau live (évite abus ressources serveur)
@@ -56,12 +58,14 @@
```
**Détection violations** :
- **Signalement utilisateurs** : bouton "Signaler" accessible pendant live
- **IA audio fingerprint** : détection musique protégée en arrière-plan (post-MVP, voir [Section 18](detection-contenu-protege.md))
- **Modération réactive** : modérateurs peuvent écouter lives signalés en temps réel
- **Coupure immédiate** : modérateur peut arrêter live si contenu illégal évident
**Justification** :
- **Buffer 15s** : équilibre entre test qualité et friction minimale
- **Notification abonnés** : engagement maximal, valeur ajoutée live
- **8h max** : couvre 99% cas usage (podcasts longs, émissions radio) sans abus
@@ -117,11 +121,13 @@
| **Modifier replay** | ❌ Non | Intégrité enregistrement |
**Conservation fichier source** :
- Opus raw conservé **7 jours** après fin live (backup)
- Suppression automatique après 7j (économie stockage)
- Si replay supprimé par créateur → fichier raw supprimé immédiatement
**Justification** :
- **Compte à rebours 5s** : outro propre, pas de coupure brutale
- **Tolérance 60s** : évite arrêts intempestifs (tunnel, changement cellule)
- **Enregistrement auto** : valorisation contenu éphémère, génération contenu pérenne
@@ -172,6 +178,7 @@
**Décision ferme** : ❌ **Aucun chat en direct, ni maintenant ni dans le futur**
**Raisons** :
- **Sécurité routière** : pas de distraction en voiture (focus UX)
- **Harcèlement** : évite contenu haineux, insultes, trolling
- **Modération** : pas de coût modération temps réel (impossible à scale)
@@ -189,9 +196,11 @@
| **Réactions emoji** | ❌ | Jamais implémenté (décision définitive) |
**Messages utilisateur** :
- "💬 Les discussions ne sont pas disponibles sur RoadWave pour garantir votre sécurité en voiture et éviter le harcèlement."
**Justification décision définitive** :
- **UX cohérente** : RoadWave = écoute en conduisant, pas réseau social interactif
- **Bien-être** : évite toxicité, harcèlement, haine (fléau réseaux sociaux)
- **Juridique** : pas de risque contentieux modération chat (DSA EU)
@@ -223,6 +232,7 @@ Auditeurs (App mobile, HLS natif)
6. **Post-live** → Job async : Opus → MP3 256 kbps → Publication replay
**Dépendances** :
-**Pion WebRTC** (Go library, open source, MIT license)
-**FFmpeg** (conversion audio, LGPL/GPL)
-**NGINX** (cache et distribution HLS, open source)
@@ -230,6 +240,7 @@ Auditeurs (App mobile, HLS natif)
-**PostgreSQL + Redis** (métadonnées live + cache)
**Avantages** :
- ✅ Pas de dépendance Google/Facebook/Cloudflare (souveraineté)
- ✅ WebRTC standard ouvert (Pion = lib Go pure)
- ✅ Réutilise infra HLS existante (pas de doublon)
@@ -245,6 +256,7 @@ Auditeurs (App mobile, HLS natif)
| **Scale** | 1M-10M | Kubernetes auto-scale (2000+ lives) | +1K€ + bande passante |
**Bande passante** :
- Live : 48 kbps × nb_auditeurs (via NGINX Cache, segments)
- Exemple : 100 auditeurs = 4.8 Mbps = ~2 Go/heure via cache
- Coût estimé : ~0.02€/heure pour 100 auditeurs

View File

@@ -35,6 +35,7 @@ Le domaine **Moderation** gère la modération des contenus et des utilisateurs,
## Ubiquitous Language
**Termes métier du domaine** :
- **Report** : Signalement d'un contenu ou utilisateur problématique
- **Strike** : Avertissement comptabilisé (3 strikes = ban)
- **Sanction** : Mesure disciplinaire (warning, suspension, ban)

View File

@@ -1,6 +1,6 @@
# Modèle de données - Modération
📖 Voir [Règles métier - Section 14 : Modération Flows](../rules/moderation-flows.md) | [Entités globales](../../_shared/entities/modele-global.md)
📖 Voir [Règles métier - Section 14 : Modération Flows](../rules/moderation-flows.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
## Diagramme

View File

@@ -67,6 +67,7 @@ flowchart TD
## Légende
**Priorités de traitement** :
- 🔴 **CRITIQUE** (score ≥90) : <2h - Violence, suicide, danger immédiat
- 🟠 **HAUTE** (70-89) : <24h - Haine, harcèlement
- 🟡 **MOYENNE** (40-69) : <24h - Spam, contenu inapproprié

View File

@@ -9,6 +9,7 @@
**Disponibilité** : Partout dans l'application
**Emplacements** :
- Player en lecture (bouton dans contrôles)
- Page profil créateur (sur chaque contenu)
- Liste de recherche (menu contextuel)
@@ -17,6 +18,7 @@
**Icône** : ⬆️ (universelle iOS/Android)
**Menu options** :
- Copier le lien
- WhatsApp
- Email
@@ -24,6 +26,7 @@
- Plus... (sheet natif OS)
**Justification** :
- Viralité = croissance organique gratuite
- Aucune friction, partage universel
@@ -93,11 +96,13 @@ Page web responsive
```
**Deep linking** :
- iOS : Universal Links (configuration `apple-app-site-association`)
- Android : App Links (configuration `assetlinks.json`)
- URL scheme : `roadwave://content/[content_id]`
**Justification** :
- Meilleure viralité (partage social optimisé)
- SEO (contenus indexés Google)
- UX optimale (web + app)
@@ -137,10 +142,12 @@ Page web responsive
- Rejouer les 30 premières secondes (illimité)
**Tracking** :
- Métriques créateur : "Partages Premium" + "Conversions Premium"
- Créateur touche sa part si conversion (70%)
**Justification** :
- Équilibre viralité / monétisation
- 30s = assez pour donner envie, pas assez pour satisfaire
- Protège revenus créateurs
@@ -219,10 +226,12 @@ Page web responsive
| **Par tag** | Filtre multi-sélection tags |
**Recherche locale** :
- Barre recherche dans profil : "Rechercher dans les contenus de @pseudo"
- Recherche full-text sur titres + descriptions
**Actions menu [•••]** :
- Partager profil
- Signaler profil (spam, usurpation)
- Bloquer créateur (masque tous ses contenus)
@@ -254,6 +263,7 @@ Page web responsive
| **Démographie** | Âge / zone géo (agrégée, anonymisée) |
**Justification** :
- Arrondi = évite comparaisons anxiogènes
- Preuve sociale pour nouveaux auditeurs (trust)
- Gamification douce (motivation créateurs)
@@ -272,6 +282,7 @@ Page web responsive
3. **Communauté significative** : ≥10K abonnés + compte actif >6 mois
**Affichage** :
- Badge bleu **✓** accolé au pseudo (partout : profil, player, recherche)
- Tooltip au survol/appui long : "Compte vérifié"
@@ -284,11 +295,13 @@ Page web responsive
| **Automatique (10K)** | Badge attribué automatiquement à 10K abonnés si compte >6 mois |
**Retrait du badge** :
- Suspension monétisation → badge retiré temporairement
- Strikes multiples → badge retiré définitivement
- Usurpation identité détectée → ban + retrait
**Justification** :
- Combat usurpations d'identité
- Trust auditeurs (surtout pour médias/personnalités)
- Simplicité (1 seul badge, pas de gamification excessive)
@@ -332,6 +345,7 @@ LIMIT 20;
```
**Champs indexés** :
- Titre du contenu (poids × 3)
- Description (poids × 1)
- Pseudo créateur (poids × 2)
@@ -350,10 +364,12 @@ LIMIT 20;
**Coût** : 0€ (PostgreSQL natif)
**Migration future** :
- Si >100K contenus : Meilisearch (typo-tolerance avancée, ~20-50€/mois)
- Si >1M contenus : Elasticsearch cluster
**Justification** :
- PostgreSQL full-text = performant jusqu'à 500K contenus
- Stemming français natif
- 0€, aucune dépendance externe
@@ -411,15 +427,18 @@ ORDER BY distance ASC;
```
**Affichage résultats** :
- Tri par défaut : distance croissante
- Indication distance : "À 2.3 km" / "À 15 km" / "À 142 km"
- Option carte : markers cliquables (clustering si >50 résultats)
**Coût** :
- MVP : 0€ (Nominatim public)
- Scale : 20-50€/mois (Nominatim self-hosted Docker)
**Justification** :
- Essentiel pour tourisme / planification trajet
- OpenStreetMap = pas de dépendance Google
- PostGIS = performant (index GIST natif)
@@ -520,6 +539,7 @@ CREATE INDEX idx_content_tags ON contents USING GIN(tags);
**Coût** : 0€ (PostgreSQL + index standards)
**Justification** :
- Filtres essentiels pour découvrabilité
- Combinables = puissance maximale
- Sauvegarde = gain temps utilisateurs réguliers
@@ -575,17 +595,20 @@ CREATE INDEX idx_content_tags ON contents USING GIN(tags);
| **Distance** | Si recherche géo : "À 2.3 km" |
**Actions contextuelles [⋮]** :
- Partager
- Ajouter à une playlist (future feature)
- Télécharger (offline)
- Signaler
**Pagination** :
- **20 résultats** par page
- Infinite scroll (charger automatiquement si scroll >80%)
- Bouton "Charger 20 suivants" en bas (fallback si scroll auto désactivé)
**Vue carte (alternative)** :
- Bouton toggle "Liste / Carte"
- Map Leaflet (OpenStreetMap)
- Markers cliquables → popup avec preview
@@ -594,6 +617,7 @@ CREATE INDEX idx_content_tags ON contents USING GIN(tags);
**Coût** : 0€ (Leaflet open source + OSM tiles gratuit)
**Justification** :
- Équilibre information / compacité
- Lazy loading = performances
- Infinite scroll = UX moderne

View File

@@ -3,6 +3,7 @@
**Contexte** : Système de gamification pour encourager les utilisateurs à signaler du contenu inapproprié de manière pertinente et qualitative.
**Objectifs** :
- Améliorer la qualité des signalements (réduire les signalements abusifs)
- Réduire la charge de travail des modérateurs (priorisation automatique)
- Récompenser les contributeurs actifs et fiables
@@ -22,6 +23,7 @@
| 🥇 | **Contributeur Or** | 50 signalements validés + 90% taux pertinence | Signalements prioritaires (+30 points) + Badge visible + Réduction Premium |
**Règles d'éligibilité** :
- Minimum **10 signalements envoyés** pour être éligible aux badges
- Les signalements "En cours" ne comptent pas dans le calcul
- Les signalements rejetés font baisser le taux de pertinence
@@ -32,10 +34,12 @@ Taux de pertinence = (Signalements validés / Total signalements envoyés) × 10
```
**Période de calcul** :
- Seuls les **6 derniers mois** comptent (période glissante)
- Évite que les utilisateurs se reposent sur leurs lauriers
**Justification** :
- **Simple** : 3 niveaux seulement (pas d'over-engineering)
- **Gratuit** : logique backend + affichage frontend
- **Efficace** : incite la qualité plutôt que la quantité
@@ -52,6 +56,7 @@ Taux de pertinence = (Signalements validés / Total signalements envoyés) × 10
| **Argent → Or** | 60 jours |
**Justification** :
- Évite la montée en badge trop rapide (anti-farming)
- Force une contribution régulière sur la durée
- Détecte les patterns suspects (audit modérateur si trop rapide)
@@ -63,6 +68,7 @@ Taux de pertinence = (Signalements validés / Total signalements envoyés) × 10
**IMPORTANT** : L'utilisateur doit être informé du système de récompenses **dès son premier signalement**.
**Moment d'affichage** :
- Après avoir envoyé le **premier signalement**
- Juste après le toast de confirmation standard
- **2 secondes de délai** avant affichage de la modal
@@ -102,6 +108,7 @@ Taux de pertinence = (Signalements validés / Total signalements envoyés) × 10
```
**Lien "En savoir plus"** :
- Redirection vers page dédiée expliquant :
- Calcul détaillé du taux de pertinence
- Critères d'obtention pour chaque badge
@@ -116,6 +123,7 @@ Taux de pertinence = (Signalements validés / Total signalements envoyés) × 10
#### 19.1.4 Affichage badges et statistiques
**Badge visible** :
- **Profil utilisateur** : visible par tous les autres utilisateurs
- **Historique signalements** : visible uniquement par l'utilisateur lui-même
- **Toast après obtention** :
@@ -136,10 +144,12 @@ Prochain palier : 🥇 Contributeur Or (30 signalements validés restants)
```
**Toast après traitement signalement** :
- Si validé : "✅ Bravo ! Votre signalement a aidé la communauté. Progression : 3/5 pour badge Bronze 🥉"
- Si rejeté : "❌ Signalement non retenu. Taux de pertinence : 60%. Continuez vos efforts !"
**Justification** :
- **Transparence totale** : l'utilisateur voit sa progression en temps réel
- **Motivation** : gamification saine (pas de pression, juste encouragement)
- **Gratifiant** : messages positifs valorisant la contribution
@@ -157,6 +167,7 @@ Score fiabilité = min(100, (Validés × 10 - Rejetés × 5 + Bonus_Or × 20))
```
**Détails** :
- **Validés** : nombre de signalements validés par modérateurs
- **Rejetés** : nombre de signalements rejetés
- **Bonus_Or** : +20 points si badge Or actif
@@ -181,10 +192,12 @@ Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_sig
```
**Intégration** :
- **Fiabilité_signaleur** = Score fiabilité / 100 (normalisé 0-1)
- Les signalements des utilisateurs avec badge Argent/Or passent automatiquement devant les autres (même score IA)
**Affichage à l'utilisateur** :
- **NON affiché** publiquement (risque de gamification abusive)
- **Visible** uniquement dans les stats personnelles :
```
@@ -201,6 +214,7 @@ Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_sig
**Décision** : Statut automatique pour utilisateurs Badge Argent ou Or
**Critère** :
- Badge **Argent OU Or** actif = automatiquement "Utilisateur de confiance"
**Avantages** :
@@ -209,6 +223,7 @@ Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_sig
3. **Notification différée** : résultats sous 12h (au lieu de 24-48h standards)
**Révocation** :
- Perte badge Argent/Or → perte statut confiance automatique
- Retour au statut normal, aucune sanction supplémentaire
@@ -259,10 +274,12 @@ Merci de contribuer à rendre RoadWave meilleur chaque jour !
```
**Rappels** :
- Email + Push J-7 : "Il vous reste 7 jours pour profiter de votre réduction Premium -50%"
- Email + Push J-1 : "Dernière chance ! Votre réduction Premium -50% expire demain"
**Si non activée après 30 jours** :
- Offre expirée (notification : "Votre offre Premium -50% a expiré")
- Badge Or conservé (seule l'offre expire, pas le badge)
@@ -271,16 +288,19 @@ Merci de contribuer à rendre RoadWave meilleur chaque jour !
#### 19.4.3 Perte du badge Or
**Conditions** :
- Taux de pertinence descend sous **90%** après audit trimestriel
- Signalements abusifs détectés (voir Section 19.5)
**Conséquences** :
- **Badge Or révoqué** immédiatement
- **Abonnement Premium en cours** reste actif jusqu'à sa fin normale
- **Nouvelle souscription** à prix normal (4.99€/mois)
- **Pas de nouvelle offre -50%** même si badge Or réobtenu ultérieurement
**Justification** :
- Maintien qualité badges sur le long terme
- Évite abus système
@@ -291,11 +311,13 @@ Merci de contribuer à rendre RoadWave meilleur chaque jour !
**Coût maximum** : **200€/mois** (si 50 utilisateurs Or simultanés avec réduction active)
**ROI attendu** :
- **1 utilisateur Or** = économie ~5-10h modération/mois = 75-150€ économisés (taux horaire modérateur ~15€/h)
- **10 utilisateurs Or actifs** = 750-1500€ économisés > 200€ coût réductions
- **ROI positif dès 2-3 utilisateurs Or actifs**
**Justification** :
- **Incitation forte** pour meilleurs contributeurs uniquement
- **Conversion** : utilisateurs gratuits très engagés → Premium payant après 3 mois
- **Risque limité** : coût max plafonné, largement compensé par économie modération
@@ -311,11 +333,13 @@ Merci de contribuer à rendre RoadWave meilleur chaque jour !
**Décision** : Maximum 10 signalements / 24h par utilisateur
**Règles** :
- Au-delà de 10 signalements/24h → signalements automatiquement rejetés
- Alerte modérateur automatique (enquête manuelle)
- Message utilisateur : "Limite quotidienne atteinte (10 signalements/24h). Réessayez demain."
**Justification** :
- Évite signalement massif (farming)
- 10/jour = largement suffisant pour usage légitime
- Coût : 0€
@@ -336,10 +360,12 @@ HAVING COUNT(*) > 30
```
**Action** :
- Enquête manuelle modérateur
- Révocation badge si abus confirmé
**Patterns détectés** :
- Signalement massif (>30/semaine)
- Taux de pertinence <50% malgré volume élevé
- Signalements tous rejetés sur période 7 jours
@@ -368,10 +394,12 @@ HAVING COUNT(*) > 30
```
**Après audit** :
- Email résultat : "Badge conservé ✓" ou "Badge révoqué ✗"
- Possibilité de réobtenir badge ultérieurement (pas de ban)
**Justification** :
- Maintien qualité badges sur le long terme
- Évite repos sur lauriers
- Coût : 0€ (script automatique)
@@ -387,6 +415,7 @@ HAVING COUNT(*) > 30
| **Grave** | Signalements massifs coordonnés (farming) | Ban permanent fonctionnalité signalement + révocation tous badges |
**Notification sanction** :
- Email + Push + In-app
- Explication détaillée de l'abus détecté
- Durée sanction
@@ -416,11 +445,13 @@ HAVING COUNT(*) > 30
---
**Conformité** :
- ✅ RGPD : données modération anonymisées après 3 ans
- ✅ Transparence : utilisateur informé dès le 1er signalement
- ✅ Anti-discrimination : système accessible à tous, basé uniquement sur pertinence
**Scalabilité** :
- 0-100 utilisateurs actifs : système automatique, 0€
- 100-1000 utilisateurs actifs : 0-50€/mois (quelques badges Or)
- 1000+ utilisateurs actifs : 50-200€/mois (max 50 badges Or simultanés)

View File

@@ -19,6 +19,7 @@ Liste déroulante avec 7 options :
| 🔧 **Autre** | Champ texte obligatoire si sélectionné |
**Justification** :
- Équilibre entre simplicité (pas trop de choix) et précision (aide les modérateurs)
- Coût : 0€ (liste déroulante standard)
@@ -33,6 +34,7 @@ Liste déroulante avec 7 options :
- Non bloquant : le signalement peut être envoyé sans commentaire
**Justification** :
- Encourage la qualité des signalements sans créer de friction
- Aide les modérateurs à comprendre le contexte
- Pas de risque d'abandon du processus
@@ -44,16 +46,19 @@ Liste déroulante avec 7 options :
**Décision** : Toast in-app avec lien historique
**Affichage** :
- Toast notification : "✓ Signalement envoyé. Nous l'examinerons sous 24-48h."
- Durée affichage : 5 secondes
- Bouton optionnel "Voir mes signalements" (accès historique)
**Historique personnel** :
- Liste des signalements envoyés par l'utilisateur
- Statut : En cours / Traité / Rejeté
- Notification in-app si action prise (contenu retiré, signalement rejeté)
**Justification** :
- Transparence maximale
- Coût : 0€ (aucun email automatique)
- Bonne UX
@@ -85,15 +90,18 @@ Liste déroulante avec 7 options :
4. Priorisation automatique selon score
**Délais** :
- Audio <5 min : 1-3 minutes
- Audio 5-30 min : 3-10 minutes
- Audio >30 min : 10-20 minutes
**Coût** :
- **MVP** : 0€ (CPU standard, processing asynchrone)
- **Scale** : 50-200€/mois (GPU VPS si >1000 signalements/jour)
**Justification** :
- 100% open source, pas de dépendance GAFAM
- Coût maîtrisé (scaling progressif)
- Gain productivité modérateurs ×3-5
@@ -112,10 +120,12 @@ Liste déroulante avec 7 options :
| **BASSE** | <72h (jours ouvrés) | Qualité audio, tags incorrects → Modérateur junior |
**Traitement automatique** :
- Score IA >95% + catégorie évidente (ex: spam répété) → Action automatique immédiate
- Notification créateur + possibilité d'appel
**Justification** :
- Réaliste et conforme DSA (Digital Services Act)
- Scalable : priorisation automatique
- Ressources humaines optimisées
@@ -133,17 +143,20 @@ Priorité = (Score_IA × 0.7) + (Signalements_cumulés × 0.2) + (Fiabilité_sig
```
**Détails** :
- **Score_IA** : 0-100% (confiance analyse automatique)
- **Signalements_cumulés** : nombre de signalements du même contenu (boost priorité)
- **Fiabilité_signaleur** : score utilisateur (historique signalements pertinents)
**Classification résultante** :
- Priorité ≥90 → **CRITIQUE** (traitement immédiat)
- Priorité 70-89 → **HAUTE** (file prioritaire)
- Priorité 40-69 → **MOYENNE** (file normale)
- Priorité <40 → **BASSE** (file différée)
**Justification** :
- Optimise le temps des modérateurs
- Traite les cas graves en priorité
- Coût : 0€ (algorithme simple)
@@ -186,11 +199,13 @@ L'équipe RoadWave
```
**Coût** :
- Email : ~0.001€/notification (Brevo, Resend)
- Push : 0€ (APNS / FCM natifs)
- In-app : 0€
**Justification** :
- Conformité DSA (transparence obligatoire)
- Multi-canal garantit réception
- Coût négligeable
@@ -232,6 +247,7 @@ L'équipe RoadWave
```
**Justification** :
- Transparence maximale (obligation DSA)
- Créateur comprend l'erreur → amélioration future
- Réduit les appels non fondés
@@ -243,6 +259,7 @@ L'équipe RoadWave
**Décision** : Formulaire in-app structuré
**Accès** :
- Bouton "Contester cette décision" dans notification
- Section "Mes sanctions" dans profil créateur
@@ -256,15 +273,18 @@ L'équipe RoadWave
| **Preuves** | Upload fichiers (max 5, 10 MB total) | ❌ |
**Après soumission** :
- Génération numéro de ticket unique (ex: `#MOD-2026-00142`)
- Email confirmation : "Votre appel sera traité sous 72h"
- Statut visible dans l'app : "En cours d'examen"
**Délai de soumission** :
- Maximum **7 jours** après notification de sanction
- Après 7 jours : appel automatiquement refusé
**Justification** :
- Professionnel et traçable
- Intégration complète avec système modération
- Coût : 0€ (formulaire custom backend)
@@ -284,6 +304,7 @@ L'équipe RoadWave
| **Critique** | 24h (cas suspension longue/ban) | Admin modération |
**Notification intermédiaire** (si délai >72h) :
- Email J+3 : "Votre appel #MOD-XXX est en cours d'examen approfondi. Réponse sous 2 jours."
**Réponse finale** :
@@ -295,10 +316,12 @@ Email détaillé avec :
4. **Définitif** : mention "Cette décision est définitive" (pas de second appel)
**Suivi in-app** :
- Mise à jour statut : "Appel accepté ✓" ou "Appel rejeté ✗"
- Badge notification
**Justification** :
- Équilibre entre rapidité et qualité de traitement
- Conforme pratiques industrie (YouTube, TikTok : 5-7 jours)
- Ressources humaines réalistes
@@ -329,6 +352,7 @@ Email détaillé avec :
5. **Fil d'activité** : actions récentes équipe (temps réel)
**Coût infrastructure** :
- MVP : 0-50€/mois (serveur CPU)
- Scale : 50-200€/mois (GPU + Redis Cluster)
@@ -337,20 +361,24 @@ Email détaillé avec :
### 14.5 Modération préventive (rappel)
**Nouveaux créateurs** :
- Validation manuelle des **3 premiers contenus**
- Délai : 24-48h (jours ouvrés)
- Transcription automatique pour aide modérateur
**Score de confiance** :
- Évolution dynamique selon historique
- Créateur fiable (0 strike depuis 6 mois) → validation automatique
- Créateur suspect (strikes récents) → validation manuelle systématique
**Publicités** :
- Validation manuelle obligatoire 24-48h (responsabilité juridique)
- Transcription + analyse métadonnées (ciblage, durée, volume)
**Justification** :
- Prévention > réaction (économie modération)
- Qualité plateforme préservée dès le début
@@ -375,11 +403,13 @@ Email détaillé avec :
**Coût total MVP** : **0-200€/mois** (infrastructure IA optionnelle)
**Conformité** :
- ✅ DSA (Digital Services Act) : transparence, traçabilité, délais
- ✅ RGPD : données modération anonymisées après 3 ans
- ✅ Logs audit : toutes actions tracées (obligation légale plateforme)
**Scalabilité** :
- 0-1000 signalements/mois : équipe 1-2 modérateurs junior + 1 senior
- 1000-10K signalements/mois : équipe 5-10 modérateurs + IA GPU
- 10K+ signalements/mois : équipe dédiée + IA optimisée + modération communautaire

View File

@@ -64,6 +64,7 @@ stateDiagram-v2
## Légende
**États principaux** :
- **Reçu** : Signalement initial (<1s)
- **EnTranscription** : Whisper large-v3 (1-20 min)
- **EnAnalyseIA** : Score confiance 0-100% (<1 min)

View File

@@ -22,6 +22,7 @@ Le domaine **Monetization** gère la monétisation des créateurs de contenu via
## Ubiquitous Language
**Termes métier du domaine** :
- **Creator Monetization** : Activation de la monétisation pour un créateur
- **KYC Verification** : Vérification d'identité requise pour versements
- **Revenue Share** : Partage de revenus (70% créateur / 30% plateforme)

Some files were not shown because too many files have changed in this diff Show More