Merge branch 'refactor/docs-reorganization' into main
This commit is contained in:
456
docs/CONTRIBUTING.md
Normal file
456
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 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
|
||||
@@ -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)
|
||||
@@ -19,7 +19,7 @@ Le domaine **Shared** constitue le **Core Domain** de RoadWave. Il contient les
|
||||
|
||||
## Modèle de données
|
||||
|
||||
- [Diagramme entités globales](entities/../entities/entities-overview.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
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Séquence - Suppression de compte
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Utilisateur
|
||||
participant API as Backend API
|
||||
participant DB as PostgreSQL
|
||||
participant E as Email
|
||||
participant J as Job Quotidien
|
||||
|
||||
U->>API: DELETE /account (demande suppression)
|
||||
API->>DB: UPDATE account_status=grace_period
|
||||
API->>DB: UPDATE deletion_requested_at=NOW()
|
||||
API->>DB: UPDATE sessions.revoked_at=NOW() (toutes)
|
||||
API->>DB: UPDATE contents (cachés, non diffusés)
|
||||
API->>E: Email avec lien annulation (30j)
|
||||
API-->>U: Compte désactivé
|
||||
|
||||
alt Utilisateur change d'avis
|
||||
U->>API: GET /account/cancel-deletion (lien email)
|
||||
API->>DB: UPDATE account_status=active
|
||||
API->>DB: UPDATE deletion_requested_at=NULL
|
||||
API->>DB: Réactivation contenus
|
||||
API->>E: Email confirmation annulation
|
||||
API-->>U: Compte réactivé
|
||||
else Après 30 jours
|
||||
J->>DB: SELECT users WHERE grace_period > 30j
|
||||
J->>DB: UPDATE account_status=deleted
|
||||
J->>DB: Anonymisation données (email, pseudo...)
|
||||
J->>DB: UPDATE contents.creator="Utilisateur supprimé"
|
||||
J->>DB: DELETE listening_history, location_history, sessions
|
||||
J->>DB: Conservation contenus anonymisés
|
||||
J-->>DB: Suppression complète
|
||||
end
|
||||
```
|
||||
|
||||
## Légende
|
||||
|
||||
**Grace period** : 30 jours pour annuler
|
||||
**Annulation** : Via lien email unique
|
||||
**Anonymisation** :
|
||||
- Données perso supprimées (email, pseudo, GPS...)
|
||||
- Contenus conservés anonymes (intérêt communauté)
|
||||
- Irréversible après 30j
|
||||
|
||||
**Alternative** : Purge auto inactivité 5 ans (notifications 90j/30j/7j avant)
|
||||
@@ -20,9 +20,9 @@ sequenceDiagram
|
||||
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->>DB: INSERT session (hash tokens, IP, device)
|
||||
A->>U: Connexion réussie
|
||||
```
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
# Séquence - Export de données RGPD
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Utilisateur
|
||||
participant API as Backend API
|
||||
participant DB as PostgreSQL
|
||||
participant W as Worker
|
||||
participant S as OVH Storage
|
||||
participant E as Email
|
||||
|
||||
U->>API: POST /exports (demande)
|
||||
API->>DB: Vérification limite (1/mois)
|
||||
API->>DB: INSERT export (status=pending)
|
||||
API->>W: Job asynchrone
|
||||
API-->>U: Export en préparation
|
||||
|
||||
W->>DB: Collecte données (profil, écoutes, contenus...)
|
||||
W->>W: Génération JSON + HTML
|
||||
|
||||
loop Contenus audio
|
||||
W->>DB: SELECT audio_url
|
||||
W->>W: Copie fichier
|
||||
end
|
||||
|
||||
W->>W: Création ZIP
|
||||
W->>S: Upload fichier
|
||||
S-->>W: URL signée (7j)
|
||||
|
||||
W->>DB: UPDATE export (status=ready, url, size)
|
||||
W->>E: Email avec lien download
|
||||
E-->>U: Export prêt (expire 7j)
|
||||
|
||||
U->>S: GET /export-signed-url
|
||||
S-->>U: Téléchargement ZIP
|
||||
|
||||
Note over DB: Job quotidien
|
||||
DB->>S: DELETE exports expirés (> 7j)
|
||||
```
|
||||
|
||||
## Légende
|
||||
|
||||
**Conformité RGPD** : Délai max 48h (Article 20)
|
||||
**Format** : ZIP (JSON machine-readable + HTML human-readable + audio)
|
||||
**Limite** : 1 export/mois
|
||||
**Expiration** : 7 jours calendaires
|
||||
**Sécurité** : URL signée unique
|
||||
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
|
||||
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`
|
||||
42
docs/domains/_shared/states/suppression-compte.md
Normal file
42
docs/domains/_shared/states/suppression-compte.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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
|
||||
@@ -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/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 16 : Publicités](../rules/publicites.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -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/entities-overview.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/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 12 : Radio Live](../rules/radio-live.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -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/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 14 : Modération Flows](../rules/moderation-flows.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Modèle de données - Monétisation créateurs
|
||||
|
||||
📖 Voir [Règles métier - Section 18 : Monétisation](../rules/monetisation-createurs.md) | [Entités globales](../../_shared/entities/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 18 : Monétisation](../rules/monetisation-createurs.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Modèle de données - Premium
|
||||
|
||||
📖 Voir [Règles métier - Section 17 : Premium](../rules/premium.md) | [Entités globales](../../_shared/entities/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 17 : Premium](../rules/premium.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Modèle de données - Recommandation
|
||||
|
||||
📖 Voir [Règles métier - Section 03 : Centres d'intérêt](../rules/centres-interet-jauges.md) | [Section 04 : Algorithme](../rules/algorithme-recommandation.md) | [Entités globales](../../_shared/entities/entities-overview.md)
|
||||
📖 Voir [Règles métier - Section 03 : Centres d'intérêt](../rules/centres-interet-jauges.md) | [Section 04 : Algorithme](../rules/algorithme-recommandation.md) | [Entités globales](../../_shared/entities/vue-ensemble.md)
|
||||
|
||||
## Diagramme
|
||||
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
# Vue d'ensemble des Features Gherkin - Modération
|
||||
|
||||
Ce document récapitule l'organisation complète des features Gherkin pour le système de modération de RoadWave, alignées avec les règles métier définies dans :
|
||||
- [14-moderation-flows.md](domains/moderation/rules/moderation-flows.md)
|
||||
- [15-moderation-communautaire.md](domains/moderation/rules/moderation-communautaire.md)
|
||||
|
||||
## Structure des Features
|
||||
|
||||
### 📱 Features UI (Interface Mobile Flutter)
|
||||
|
||||
**Localisation** : `features/ui/moderation/`
|
||||
|
||||
| Feature | Description | Règles métier couvertes |
|
||||
|---------|-------------|------------------------|
|
||||
| **signalement-ui.feature** | Interface de signalement mobile (formulaire, toast, découverte badges) | 14.1.1, 14.1.2, 14.1.3, 19.1.3 |
|
||||
| **historique-signalements.feature** | Historique personnel des signalements de l'utilisateur | 14.1.3 |
|
||||
| **badges-statistiques.feature** | Affichage des badges, statistiques et gamification | 19.1.4, 19.2, 19.3, 19.4 |
|
||||
| **sanctions-appel.feature** | Interface de consultation des sanctions et processus d'appel | 14.3.1, 14.3.2, 14.3.3, 14.3.4 |
|
||||
|
||||
**Couverture** :
|
||||
- ✅ Formulaire de signalement avec 7 catégories
|
||||
- ✅ Toast de confirmation et modal de découverte
|
||||
- ✅ Historique des signalements avec statuts
|
||||
- ✅ Badges Bronze/Argent/Or et progression visuelle
|
||||
- ✅ Statistiques personnelles et taux de pertinence
|
||||
- ✅ Notifications multi-canal des sanctions
|
||||
- ✅ Formulaire d'appel structuré
|
||||
- ✅ Dark mode et accessibilité complète
|
||||
|
||||
### 🖥️ Features Admin (Dashboard Modérateur Web)
|
||||
|
||||
**Localisation** : `features/admin/moderation/`
|
||||
|
||||
| Feature | Description | Règles métier couvertes |
|
||||
|---------|-------------|------------------------|
|
||||
| **dashboard-moderateur.feature** | Dashboard principal de modération avec files d'attente | 14.2.2, 14.2.3, 14.4 |
|
||||
| **outils-moderateur.feature** | Outils modérateur (player audio, transcription, historique créateur, actions) | 14.2.1, 14.4 |
|
||||
|
||||
**Couverture** :
|
||||
- ✅ Files d'attente par priorité (CRITIQUE/HAUTE/MOYENNE/BASSE)
|
||||
- ✅ Statistiques temps réel et SLA
|
||||
- ✅ Player audio Wavesurfer.js avec waveform
|
||||
- ✅ Transcription synchronisée avec surlignage
|
||||
- ✅ Analyse IA (Whisper + NLP) avec scores de confiance
|
||||
- ✅ Historique créateur 360° (strikes, contenus, patterns)
|
||||
- ✅ Actions rapides avec raccourcis clavier
|
||||
- ✅ Logs d'audit conformes DSA
|
||||
|
||||
### 🔌 Features API (Backend Go)
|
||||
|
||||
**Localisation** : `features/api/moderation/`
|
||||
|
||||
| Feature | Description | Règles métier couvertes | Statut |
|
||||
|---------|-------------|------------------------|--------|
|
||||
| **signalement.feature** | API de signalement de contenu | 14.1 | ✅ Existant - Complet |
|
||||
| **traitement-signalements.feature** | Traitement IA et priorisation | 14.2 | ✅ Existant - Complet |
|
||||
| **sanctions-notifications.feature** | Sanctions et notifications multi-canal | 14.3 | ✅ Existant - Complet |
|
||||
| **moderation-preventive.feature** | Validation manuelle premiers contenus | 14.5 | ✅ Existant - Complet |
|
||||
| **moderation-communautaire.feature** | Badges, scores de fiabilité, anti-abus | 19 | ✅ Existant - Complet |
|
||||
|
||||
**Couverture** :
|
||||
- ✅ 7 catégories de signalement + commentaire optionnel
|
||||
- ✅ IA pré-filtre (Whisper large-v3 + distilbert + roberta)
|
||||
- ✅ SLA progressif : <2h CRITIQUE, <24h HAUTE, <72h BASSE
|
||||
- ✅ Priorisation automatique (formule IA + signalements cumulés + fiabilité)
|
||||
- ✅ Notifications multi-canal (push + in-app + email)
|
||||
- ✅ Processus d'appel avec délai 7 jours et SLA 72h
|
||||
- ✅ 3 niveaux de badges (Bronze/Argent/Or)
|
||||
- ✅ Score de fiabilité et utilisateurs de confiance
|
||||
- ✅ Réduction Premium -50% pour badge Or
|
||||
- ✅ Anti-abus : limite 10/24h, audit trimestriel, sanctions
|
||||
|
||||
## Mapping Règles Métier → Features
|
||||
|
||||
### Section 14.1 - Signalement
|
||||
|
||||
| Règle | Feature API | Feature UI |
|
||||
|-------|------------|-----------|
|
||||
| 14.1.1 Catégories (7) | signalement.feature | signalement-ui.feature |
|
||||
| 14.1.2 Commentaire optionnel | signalement.feature | signalement-ui.feature |
|
||||
| 14.1.3 Confirmation + historique | signalement.feature | signalement-ui.feature, historique-signalements.feature |
|
||||
|
||||
### Section 14.2 - Traitement
|
||||
|
||||
| Règle | Feature API | Feature Admin |
|
||||
|-------|------------|--------------|
|
||||
| 14.2.1 IA pré-filtre (Whisper + NLP) | traitement-signalements.feature | outils-moderateur.feature |
|
||||
| 14.2.2 Délais SLA | traitement-signalements.feature | dashboard-moderateur.feature |
|
||||
| 14.2.3 Priorisation automatique | traitement-signalements.feature | dashboard-moderateur.feature |
|
||||
|
||||
### Section 14.3 - Sanctions
|
||||
|
||||
| Règle | Feature API | Feature UI |
|
||||
|-------|------------|-----------|
|
||||
| 14.3.1 Notification multi-canal | sanctions-notifications.feature | sanctions-appel.feature |
|
||||
| 14.3.2 Détail sanction | sanctions-notifications.feature | sanctions-appel.feature |
|
||||
| 14.3.3 Processus d'appel | sanctions-notifications.feature | sanctions-appel.feature |
|
||||
| 14.3.4 Délai réponse appel (72h) | sanctions-notifications.feature | sanctions-appel.feature |
|
||||
|
||||
### Section 14.4 - Outils Modérateurs
|
||||
|
||||
| Règle | Feature Admin |
|
||||
|-------|--------------|
|
||||
| Dashboard modération | dashboard-moderateur.feature |
|
||||
| Player audio + waveform | outils-moderateur.feature |
|
||||
| Transcription + annotations | outils-moderateur.feature |
|
||||
| Historique créateur 360° | outils-moderateur.feature |
|
||||
| Actions rapides (A/R/E) | outils-moderateur.feature |
|
||||
| Logs audit (DSA) | outils-moderateur.feature |
|
||||
|
||||
### Section 14.5 - Modération Préventive
|
||||
|
||||
| Règle | Feature API |
|
||||
|-------|------------|
|
||||
| Validation 3 premiers contenus | moderation-preventive.feature |
|
||||
| Score de confiance dynamique | moderation-preventive.feature |
|
||||
| Publicités validation manuelle | moderation-preventive.feature |
|
||||
|
||||
### Section 19 - Modération Communautaire
|
||||
|
||||
| Règle | Feature API | Feature UI |
|
||||
|-------|------------|-----------|
|
||||
| 19.1 Badges (Bronze/Argent/Or) | moderation-communautaire.feature | badges-statistiques.feature |
|
||||
| 19.2 Score de fiabilité | moderation-communautaire.feature | - |
|
||||
| 19.3 Utilisateur de confiance | moderation-communautaire.feature | badges-statistiques.feature |
|
||||
| 19.4 Réduction Premium Or | moderation-communautaire.feature | badges-statistiques.feature |
|
||||
| 19.5 Anti-abus | moderation-communautaire.feature | - |
|
||||
|
||||
## Stack Technique Testée
|
||||
|
||||
### Backend (Features API)
|
||||
- **Framework tests** : Godog (Gherkin pour Go)
|
||||
- **Localisation step definitions** : `backend/tests/bdd/moderation/`
|
||||
- **Technologies testées** :
|
||||
- API Go + Fiber (endpoints REST)
|
||||
- PostgreSQL (stockage signalements, sanctions, badges)
|
||||
- Redis (cache, files d'attente, priorisation)
|
||||
- Whisper large-v3 (transcription audio)
|
||||
- distilbert-base-uncased (analyse sentiment)
|
||||
- facebook/roberta-hate-speech (détection haine)
|
||||
- Email (Brevo/Resend)
|
||||
- Push notifications (APNS/FCM)
|
||||
|
||||
### Frontend Mobile (Features UI)
|
||||
- **Framework tests** : flutter_gherkin + integration_test
|
||||
- **Localisation step definitions** : `mobile/tests/bdd/moderation/`
|
||||
- **Technologies testées** :
|
||||
- Flutter (UI mobile iOS/Android)
|
||||
- Widgets personnalisés (formulaires, badges, statistiques)
|
||||
- Animations (confettis, level up, toast)
|
||||
- Dark mode et accessibilité (lecteur d'écran)
|
||||
- Navigation et routing
|
||||
- Gestion d'état (Provider/Riverpod)
|
||||
|
||||
### Frontend Admin (Features Admin)
|
||||
- **Framework tests** : Cucumber.js + Playwright
|
||||
- **Localisation step definitions** : `admin-dashboard/tests/e2e/moderation/` (à créer)
|
||||
- **Technologies testées** :
|
||||
- React + TypeScript
|
||||
- TanStack Table (tableau signalements)
|
||||
- Wavesurfer.js (player audio + waveform)
|
||||
- WebSocket (temps réel)
|
||||
- Raccourcis clavier
|
||||
|
||||
## Statistiques
|
||||
|
||||
### Couverture des Règles Métier
|
||||
|
||||
| Section | Scénarios | Règles couvertes | Statut |
|
||||
|---------|-----------|------------------|--------|
|
||||
| 14.1 Signalement | 25 | 100% | ✅ |
|
||||
| 14.2 Traitement | 30 | 100% | ✅ |
|
||||
| 14.3 Sanctions | 35 | 100% | ✅ |
|
||||
| 14.4 Outils | 40 | 100% | ✅ Nouveau |
|
||||
| 14.5 Préventive | 20 | 100% | ✅ |
|
||||
| 19 Communautaire | 40 | 100% | ✅ |
|
||||
| **Total** | **190** | **100%** | ✅ |
|
||||
|
||||
### Répartition Features
|
||||
|
||||
| Type | Nombre | Scénarios totaux |
|
||||
|------|--------|------------------|
|
||||
| Features API (Backend) | 5 | ~90 |
|
||||
| Features UI (Mobile) | 4 | ~60 |
|
||||
| Features Admin (Dashboard) | 2 | ~40 |
|
||||
| **Total** | **11** | **~190** |
|
||||
|
||||
## Conformité
|
||||
|
||||
### Digital Services Act (DSA)
|
||||
- ✅ Transparence : raison détaillée + extrait + transcription
|
||||
- ✅ Délais : SLA 2h/24h/72h documentés et testés
|
||||
- ✅ Recours : processus d'appel 7 jours + réponse 72h
|
||||
- ✅ Traçabilité : logs audit complets avec conservation 3 ans
|
||||
|
||||
### RGPD
|
||||
- ✅ Anonymat signaleur vis-à-vis du créateur
|
||||
- ✅ Anonymisation des logs après 3 ans
|
||||
- ✅ Suppression données personnelles à la demande
|
||||
- ✅ Consentement notifications
|
||||
|
||||
### Accessibilité (WCAG AA)
|
||||
- ✅ Contraste couleurs (dark mode testé)
|
||||
- ✅ Labels lecteur d'écran
|
||||
- ✅ Navigation clavier
|
||||
- ✅ Tailles de police adaptatives
|
||||
|
||||
## Coûts
|
||||
|
||||
| Composant | Technologie | Coût MVP | Coût Scale |
|
||||
|-----------|-------------|----------|------------|
|
||||
| IA transcription | Whisper (self-hosted) | 0€ (CPU) | 50-200€/mois (GPU) |
|
||||
| IA analyse | distilbert + roberta | 0€ | 0€ |
|
||||
| Notifications email | Brevo/Resend | ~0.001€/email | ~10-50€/mois |
|
||||
| Notifications push | APNS/FCM | 0€ | 0€ |
|
||||
| Dashboard admin | React + TanStack | 0€ | 0€ |
|
||||
| Badges communautaires | Backend Go | 0€ | 0-200€/mois (réductions Premium) |
|
||||
| **Total** | - | **0-50€/mois** | **60-450€/mois** |
|
||||
|
||||
**ROI** : Positif dès 2-3 utilisateurs Badge Or actifs (économie modération > coût réductions Premium)
|
||||
|
||||
## Prochaines Étapes
|
||||
|
||||
1. ✅ ~~Créer features UI modération mobile~~
|
||||
2. ✅ ~~Créer features dashboard modérateur~~
|
||||
3. ⏳ Implémenter step definitions backend (Godog)
|
||||
4. ⏳ Implémenter step definitions mobile (flutter_gherkin)
|
||||
5. ⏳ Créer dashboard admin React + implémenter step definitions (Cucumber.js)
|
||||
6. ⏳ Intégrer dans CI/CD (make test-bdd)
|
||||
|
||||
## Commandes de Test
|
||||
|
||||
```bash
|
||||
# Tous les tests BDD
|
||||
make test-bdd
|
||||
|
||||
# Tests modération backend uniquement
|
||||
cd backend
|
||||
godog run ../features/api/moderation/
|
||||
|
||||
# Tests modération mobile uniquement
|
||||
cd mobile
|
||||
flutter test integration_test/moderation/
|
||||
|
||||
# Tests dashboard admin (à venir)
|
||||
cd admin-dashboard
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
## Documentation Liée
|
||||
|
||||
- [Règles métier - Modération Flows](domains/moderation/rules/moderation-flows.md)
|
||||
- [Règles métier - Modération Communautaire](domains/moderation/rules/moderation-communautaire.md)
|
||||
- [ADR-023 - Architecture Modération](adr/023-architecture-moderation.md)
|
||||
- [ADR-013 - Stratégie Tests](adr/013-strategie-tests.md)
|
||||
|
||||
---
|
||||
|
||||
**Dernière mise à jour** : 2026-02-02
|
||||
**Statut** : ✅ Features complètes - Implémentation en cours
|
||||
@@ -1,58 +0,0 @@
|
||||
# Documentation des Interfaces
|
||||
|
||||
Cette section documente les interfaces utilisateur de RoadWave (mobile et web).
|
||||
|
||||
## Organisation
|
||||
|
||||
### Mobile (Flutter)
|
||||
L'application mobile est l'interface principale pour les utilisateurs finaux (conducteurs, piétons, touristes).
|
||||
|
||||
- **[Navigation & Architecture](mobile/navigation.md)** : Structure de navigation, routing
|
||||
- **Écran d'accueil** : Feed de recommandations géolocalisées
|
||||
- **Lecteur audio** : Player HLS, contrôles, playlists
|
||||
- **Carte & découverte** : Vue carte, exploration géographique
|
||||
- **Profil & réglages** : Jauges d'intérêt, paramètres
|
||||
|
||||
### Web (Créateurs)
|
||||
L'interface web est destinée aux créateurs de contenu et aux annonceurs.
|
||||
|
||||
- **Dashboard** : Vue d'ensemble, statistiques
|
||||
- **Upload de contenu** : Import audio, métadonnées, géolocalisation
|
||||
- **Statistiques** : Analytics, revenus
|
||||
|
||||
## Conventions de documentation
|
||||
|
||||
### Captures d'écran
|
||||
Placez les images dans `docs/interfaces/assets/` et référencez-les ainsi :
|
||||
|
||||
```markdown
|
||||
{ width="300" }
|
||||
```
|
||||
|
||||
### Diagrammes de flux
|
||||
Utilisez Mermaid pour les parcours utilisateur :
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Ouverture app] --> B{GPS actif?}
|
||||
B -->|Oui| C[Chargement recommandations]
|
||||
B -->|Non| D[Demande permission]
|
||||
D --> C
|
||||
```
|
||||
|
||||
### Onglets multi-plateformes
|
||||
Pour montrer des variations iOS/Android :
|
||||
|
||||
=== "iOS"
|
||||
Comportement spécifique iOS
|
||||
|
||||
=== "Android"
|
||||
Comportement spécifique Android
|
||||
|
||||
## Principes de design
|
||||
|
||||
- **Mobile-first** : L'app mobile est l'expérience principale
|
||||
- **Géolocalisation centrale** : L'UI doit toujours contextualiser par rapport à la position
|
||||
- **Audio en arrière-plan** : Player persistant, mini-player
|
||||
- **Mode sombre** : Support obligatoire (conduite de nuit)
|
||||
- **Accessibilité** : WCAG 2.1 AA minimum
|
||||
@@ -1,208 +0,0 @@
|
||||
# Navigation & Architecture Mobile
|
||||
|
||||
## Architecture de navigation
|
||||
|
||||
L'application mobile utilise une navigation par onglets (Bottom Navigation Bar) avec 4 sections principales.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[App Shell] --> B[🏠 Accueil]
|
||||
A --> C[🗺️ Carte]
|
||||
A --> D[🎵 Bibliothèque]
|
||||
A --> E[👤 Profil]
|
||||
|
||||
B --> B1[Feed Recommandations]
|
||||
B --> B2[Lecteur Audio]
|
||||
|
||||
C --> C1[Vue Carte]
|
||||
C --> C2[Détail Point]
|
||||
|
||||
D --> D1[Mes Playlists]
|
||||
D --> D2[Contenus Sauvegardés]
|
||||
D --> D3[Historique]
|
||||
|
||||
E --> E1[Jauges Intérêt]
|
||||
E --> E2[Paramètres]
|
||||
E --> E3[Abonnements]
|
||||
```
|
||||
|
||||
## Bottom Navigation Bar
|
||||
|
||||
### Onglet 1 : Accueil 🏠
|
||||
Point d'entrée principal après le lancement de l'app.
|
||||
|
||||
**Fonctionnalités** :
|
||||
- Feed de recommandations basé sur la géolocalisation + intérêts
|
||||
- Rafraîchissement pull-to-refresh
|
||||
- Lecture directe depuis le feed
|
||||
|
||||
**Navigation vers** :
|
||||
- Détail d'un contenu (push)
|
||||
- Lecteur plein écran (modal)
|
||||
- Profil d'un créateur (push)
|
||||
|
||||
### Onglet 2 : Carte 🗺️
|
||||
Exploration visuelle des contenus par géolocalisation.
|
||||
|
||||
**Fonctionnalités** :
|
||||
- Carte interactive (MapLibre GL)
|
||||
- Markers pour contenus à proximité
|
||||
- Clusters pour zones denses
|
||||
- Recherche par adresse
|
||||
|
||||
**Navigation vers** :
|
||||
- Détail d'un point (bottom sheet)
|
||||
- Lecteur audio (modal persistant)
|
||||
|
||||
### Onglet 3 : Bibliothèque 🎵
|
||||
Accès aux contenus sauvegardés, historique, playlists.
|
||||
|
||||
**Fonctionnalités** :
|
||||
- Playlists personnelles
|
||||
- Contenus favoris
|
||||
- Historique d'écoute
|
||||
- Téléchargements offline
|
||||
|
||||
### Onglet 4 : Profil 👤
|
||||
Gestion du compte, préférences, jauges d'intérêt.
|
||||
|
||||
**Fonctionnalités** :
|
||||
- Visualisation/édition jauges d'intérêt
|
||||
- Paramètres de l'app
|
||||
- Gestion abonnement Premium
|
||||
- Déconnexion
|
||||
|
||||
## Lecteur Audio (Modal persistant)
|
||||
|
||||
Le lecteur est un composant modal qui persiste lors de la navigation entre onglets.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Collapsed: Lecture démarre
|
||||
Collapsed --> Expanded: Tap sur mini-player
|
||||
Expanded --> Collapsed: Swipe down
|
||||
Collapsed --> [*]: Stop audio
|
||||
|
||||
Expanded --> Queue: Tap "À suivre"
|
||||
Queue --> Expanded: Retour
|
||||
```
|
||||
|
||||
=== "État Collapsed (Mini-player)"
|
||||
- Hauteur : 60dp
|
||||
- Position : Bottom (au-dessus de la Bottom Nav Bar)
|
||||
- Affiche : Titre, artiste, play/pause, skip
|
||||
- Interaction : Tap pour expand
|
||||
|
||||
=== "État Expanded (Lecteur plein écran)"
|
||||
- Plein écran modal
|
||||
- Cover art grande taille
|
||||
- Contrôles avancés (repeat, shuffle, queue)
|
||||
- Scrubber timeline
|
||||
- Boutons : like, share, download, add to playlist
|
||||
|
||||
## Routing Flutter
|
||||
|
||||
### Configuration
|
||||
|
||||
Utilisation de **go_router** (voir [ADR-020](../../adr/020-librairies-flutter.md)).
|
||||
|
||||
```dart
|
||||
// Exemple simplifié
|
||||
GoRouter(
|
||||
routes: [
|
||||
ShellRoute(
|
||||
builder: (context, state, child) => AppShell(child: child),
|
||||
routes: [
|
||||
GoRoute(path: '/home', builder: (context, state) => HomeScreen()),
|
||||
GoRoute(path: '/map', builder: (context, state) => MapScreen()),
|
||||
GoRoute(path: '/library', builder: (context, state) => LibraryScreen()),
|
||||
GoRoute(path: '/profile', builder: (context, state) => ProfileScreen()),
|
||||
],
|
||||
),
|
||||
GoRoute(
|
||||
path: '/content/:id',
|
||||
builder: (context, state) => ContentDetailScreen(
|
||||
contentId: state.pathParameters['id']!,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
### Deep Links
|
||||
|
||||
L'app supporte les deep links pour :
|
||||
- `roadwave://content/{id}` : Ouvrir un contenu spécifique
|
||||
- `roadwave://map?lat={lat}&lon={lon}` : Ouvrir la carte à une position
|
||||
- `roadwave://profile` : Ouvrir le profil
|
||||
|
||||
## Gestion d'état
|
||||
|
||||
- **Provider** pour l'état global (user, player, location)
|
||||
- **BLoC** pour les features complexes (recommendation, map)
|
||||
- **Riverpod** pour l'injection de dépendances
|
||||
|
||||
Voir [ADR-020](../../adr/020-librairies-flutter.md) pour les détails.
|
||||
|
||||
## Permissions système
|
||||
|
||||
La navigation gère les demandes de permissions de manière contextuelle :
|
||||
|
||||
1. **Géolocalisation** : Demandée au premier lancement, avant d'afficher l'accueil
|
||||
2. **Notifications** : Demandée après 3 sessions utilisateur (opt-in soft)
|
||||
3. **Stockage** : Demandée lors du premier téléchargement offline
|
||||
|
||||
Voir [mobile/permissions-strategy.md](../../mobile/permissions-strategy.md) pour la stratégie complète.
|
||||
|
||||
## Transitions et animations
|
||||
|
||||
### Transitions entre onglets
|
||||
- Aucune animation (changement instantané)
|
||||
- Préservation du scroll state sur chaque onglet
|
||||
|
||||
### Navigation push/pop
|
||||
- iOS : Slide transition (standard Cupertino)
|
||||
- Android : Fade + Slide transition (Material Design)
|
||||
|
||||
### Mini-player expand/collapse
|
||||
- Spring animation (bounce léger)
|
||||
- Durée : 300ms
|
||||
- Curve : `Curves.easeInOutCubic`
|
||||
|
||||
## Gestion du back button (Android)
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Back pressed] --> B{Lecteur expanded?}
|
||||
B -->|Oui| C[Collapse lecteur]
|
||||
B -->|Non| D{Sur onglet Accueil?}
|
||||
D -->|Non| E[Retour à Accueil]
|
||||
D -->|Oui| F{Audio en cours?}
|
||||
F -->|Oui| G[Dialog: Quitter?]
|
||||
F -->|Non| H[Quitter app]
|
||||
G -->|Confirme| H
|
||||
G -->|Annule| I[Rester]
|
||||
```
|
||||
|
||||
## Accessibilité
|
||||
|
||||
- **Screen readers** : Labels sémantiques sur tous les boutons
|
||||
- **Navigation clavier** : Support complet (pour tablettes avec clavier)
|
||||
- **Contraste** : WCAG 2.1 AA minimum (4.5:1 pour texte)
|
||||
- **Taille des touch targets** : 48dp minimum
|
||||
|
||||
## Mode sombre
|
||||
|
||||
Support obligatoire du mode sombre (conduite de nuit).
|
||||
|
||||
=== "Mode Clair"
|
||||
- Fond : White / Light Gray (#F5F5F5)
|
||||
- Texte : Dark Gray (#212121)
|
||||
- Accent : Indigo (#3F51B5)
|
||||
|
||||
=== "Mode Sombre"
|
||||
- Fond : Dark (#121212)
|
||||
- Texte : White (#FFFFFF)
|
||||
- Accent : Indigo clair (#7986CB)
|
||||
|
||||
Détection automatique via `MediaQuery.platformBrightness` ou choix manuel dans les paramètres.
|
||||
Reference in New Issue
Block a user