Compare commits
19 Commits
refactor/d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2679dd381 | ||
|
|
23fe67470b | ||
|
|
ae2fc3ee6f | ||
|
|
35aaa105d0 | ||
|
|
95c65b8be1 | ||
|
|
989dc4b6cf | ||
|
|
64d6611147 | ||
|
|
5497ea793f | ||
|
|
a29718a8e8 | ||
|
|
bc9b3b6a69 | ||
|
|
20a784b15c | ||
|
|
3da7eb80a1 | ||
|
|
62fe0ed5eb | ||
|
|
4b28db3465 | ||
|
|
b52ffb8db9 | ||
|
|
fd2b0f70c5 | ||
|
|
1896c249dd | ||
|
|
e63603551d | ||
|
|
cf7a46be27 |
31
.dockerignore
Normal file
31
.dockerignore
Normal 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
4
.gitignore
vendored
@@ -64,4 +64,6 @@ config/*.local.yaml
|
||||
# MkDocs
|
||||
site/
|
||||
.cache/
|
||||
docs/bdd/
|
||||
|
||||
# Generated documentation
|
||||
docs/generated/
|
||||
|
||||
@@ -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%
|
||||
|
||||
26
Makefile
26
Makefile
@@ -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)"
|
||||
|
||||
228
TECHNICAL.md
228
TECHNICAL.md
@@ -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.
|
||||
@@ -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
462
docs/CONTRIBUTING.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
-
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
126
docs/adr/README.md
Normal 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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
14
docs/docker/mkdocs.Dockerfile
Normal file
14
docs/docker/mkdocs.Dockerfile
Normal 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"]
|
||||
@@ -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"]
|
||||
@@ -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** :
|
||||
|
||||
@@ -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
|
||||
|
||||
57
docs/domains/_shared/entities/account-deletions.md
Normal file
57
docs/domains/_shared/entities/account-deletions.md
Normal 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é"`)
|
||||
78
docs/domains/_shared/entities/breach-incidents.md
Normal file
78
docs/domains/_shared/entities/breach-incidents.md
Normal 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"]`
|
||||
56
docs/domains/_shared/entities/consents.md
Normal file
56
docs/domains/_shared/entities/consents.md
Normal 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
|
||||
56
docs/domains/_shared/entities/data-retention-logs.md
Normal file
56
docs/domains/_shared/entities/data-retention-logs.md
Normal 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
|
||||
}
|
||||
}
|
||||
```
|
||||
65
docs/domains/_shared/entities/devices.md
Normal file
65
docs/domains/_shared/entities/devices.md
Normal 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
|
||||
77
docs/domains/_shared/entities/exports.md
Normal file
77
docs/domains/_shared/entities/exports.md
Normal 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
|
||||
67
docs/domains/_shared/entities/interest-gauges.md
Normal file
67
docs/domains/_shared/entities/interest-gauges.md
Normal 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
|
||||
66
docs/domains/_shared/entities/location-history.md
Normal file
66
docs/domains/_shared/entities/location-history.md
Normal 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€
|
||||
@@ -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
|
||||
61
docs/domains/_shared/entities/parental-consents.md
Normal file
61
docs/domains/_shared/entities/parental-consents.md
Normal 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é
|
||||
54
docs/domains/_shared/entities/privacy-policy-versions.md
Normal file
54
docs/domains/_shared/entities/privacy-policy-versions.md
Normal 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)
|
||||
105
docs/domains/_shared/entities/reports.md
Normal file
105
docs/domains/_shared/entities/reports.md
Normal 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
|
||||
64
docs/domains/_shared/entities/sessions.md
Normal file
64
docs/domains/_shared/entities/sessions.md
Normal 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
|
||||
65
docs/domains/_shared/entities/user-profile-history.md
Normal file
65
docs/domains/_shared/entities/user-profile-history.md
Normal 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
|
||||
320
docs/domains/_shared/entities/vue-ensemble.md
Normal file
320
docs/domains/_shared/entities/vue-ensemble.md
Normal 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
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
56
docs/domains/_shared/sequences/anonymisation-gps.md
Normal file
56
docs/domains/_shared/sequences/anonymisation-gps.md
Normal 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)
|
||||
45
docs/domains/_shared/sequences/authentification.md
Normal file
45
docs/domains/_shared/sequences/authentification.md
Normal 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
|
||||
44
docs/domains/_shared/sequences/consentement-parental.md
Normal file
44
docs/domains/_shared/sequences/consentement-parental.md
Normal 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]`
|
||||
66
docs/domains/_shared/sequences/export-donnees.md
Normal file
66
docs/domains/_shared/sequences/export-donnees.md
Normal 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
|
||||
44
docs/domains/_shared/sequences/moderation-contenu.md
Normal file
44
docs/domains/_shared/sequences/moderation-contenu.md
Normal 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
|
||||
64
docs/domains/_shared/sequences/notification-breach.md
Normal file
64
docs/domains/_shared/sequences/notification-breach.md
Normal 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)
|
||||
40
docs/domains/_shared/sequences/refresh-token.md
Normal file
40
docs/domains/_shared/sequences/refresh-token.md
Normal 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
|
||||
51
docs/domains/_shared/sequences/signalement.md
Normal file
51
docs/domains/_shared/sequences/signalement.md
Normal 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
|
||||
52
docs/domains/_shared/sequences/suppression-compte.md
Normal file
52
docs/domains/_shared/sequences/suppression-compte.md
Normal 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
|
||||
44
docs/domains/_shared/states/account-deletion-lifecycle.md
Normal file
44
docs/domains/_shared/states/account-deletion-lifecycle.md
Normal 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
|
||||
44
docs/domains/_shared/states/breach-incident-lifecycle.md
Normal file
44
docs/domains/_shared/states/breach-incident-lifecycle.md
Normal 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`
|
||||
56
docs/domains/_shared/states/compte-utilisateur.md
Normal file
56
docs/domains/_shared/states/compte-utilisateur.md
Normal 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)
|
||||
32
docs/domains/_shared/states/consentement-parental.md
Normal file
32
docs/domains/_shared/states/consentement-parental.md
Normal 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
|
||||
39
docs/domains/_shared/states/contenu.md
Normal file
39
docs/domains/_shared/states/contenu.md
Normal 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
|
||||
38
docs/domains/_shared/states/export-donnees.md
Normal file
38
docs/domains/_shared/states/export-donnees.md
Normal 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
|
||||
44
docs/domains/_shared/states/incident-breach.md
Normal file
44
docs/domains/_shared/states/incident-breach.md
Normal 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`
|
||||
32
docs/domains/_shared/states/parental-consent-lifecycle.md
Normal file
32
docs/domains/_shared/states/parental-consent-lifecycle.md
Normal 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
|
||||
29
docs/domains/_shared/states/session.md
Normal file
29
docs/domains/_shared/states/session.md
Normal 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
|
||||
32
docs/domains/_shared/states/signalement.md
Normal file
32
docs/domains/_shared/states/signalement.md
Normal 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
|
||||
44
docs/domains/_shared/states/suppression-compte.md
Normal file
44
docs/domains/_shared/states/suppression-compte.md
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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é
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user