docs: migrer schémas BDD de Mermaid vers DBML
Remplace les diagrammes Mermaid par DBML (via kroki-dbml) pour une meilleure expressivité des schémas de base de données : - Ajout support notes, contraintes et indexes détaillés - Migration de tous les schémas d'entités partagées - Ajout fichier exemple dbml-example.md - Configuration plugin mkdocs-kroki pour rendu DBML
This commit is contained in:
@@ -137,6 +137,10 @@ make docs-pdf # Generate PDF of all documentation
|
|||||||
make docs-clean # Remove generated docs and PDF
|
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
|
## Working with sqlc
|
||||||
|
|
||||||
When adding or modifying database queries:
|
When adding or modifying database queries:
|
||||||
|
|||||||
@@ -4,21 +4,36 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o| ACCOUNT_DELETIONS : "demande"
|
id uuid [primary key]
|
||||||
|
email varchar(255)
|
||||||
|
status varchar(20)
|
||||||
|
}
|
||||||
|
|
||||||
ACCOUNT_DELETIONS {
|
Table account_deletions {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, unique, ref: - users.id, note: 'One-to-one: un user ne peut avoir qu une seule demande active']
|
||||||
string status
|
status deletion_status_enum [not null, default: 'pending']
|
||||||
string cancellation_token
|
cancellation_token varchar(64) [unique, note: 'Token dans email pour annuler (expire après 30j)']
|
||||||
timestamp requested_at
|
requested_at timestamp [not null, default: `now()`]
|
||||||
timestamp effective_at "requested_at + 30j"
|
effective_at timestamp [not null, note: 'Auto-calculated: requested_at + 30 days']
|
||||||
timestamp cancelled_at
|
cancelled_at timestamp [note: 'Timestamp annulation via lien email (NULL si non annulé)']
|
||||||
timestamp deleted_at
|
deleted_at timestamp [note: 'Timestamp suppression effective (NULL si pending/cancelled)']
|
||||||
string deletion_reason
|
deletion_reason text [note: 'Raison optionnelle fournie par l utilisateur']
|
||||||
json deleted_data_summary
|
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']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,32 +4,56 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table breach_incidents {
|
||||||
BREACH_INCIDENTS ||--o{ BREACH_AFFECTED_USERS : "impacte"
|
id uuid [primary key]
|
||||||
USERS ||--o{ BREACH_AFFECTED_USERS : "est impacté"
|
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]
|
||||||
|
|
||||||
BREACH_INCIDENTS {
|
indexes {
|
||||||
uuid id PK
|
(severity, detected_at) [note: 'Incidents par gravité et chronologie']
|
||||||
string severity "low/medium/high/critical"
|
(cnil_notification_required, cnil_notified_at) [note: 'Track CNIL notification compliance']
|
||||||
text description
|
}
|
||||||
json data_categories_affected
|
|
||||||
int estimated_users_count
|
|
||||||
timestamp detected_at
|
|
||||||
timestamp contained_at
|
|
||||||
timestamp cnil_notified_at
|
|
||||||
timestamp users_notified_at
|
|
||||||
text mitigation_actions
|
|
||||||
boolean cnil_notification_required
|
|
||||||
boolean user_notification_required
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BREACH_AFFECTED_USERS {
|
Table users {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid breach_id FK
|
}
|
||||||
uuid user_id FK
|
|
||||||
timestamp notified_at
|
Table breach_affected_users {
|
||||||
string notification_channel "email/push/sms"
|
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)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,32 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ USER_CONSENTS : "donne"
|
id uuid [primary key]
|
||||||
|
}
|
||||||
|
|
||||||
USER_CONSENTS {
|
Table user_consents {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
string consent_type
|
consent_type consent_type_enum [not null]
|
||||||
string consent_version
|
consent_version varchar(10) [not null, note: 'Format: v1.0, v2.0, etc.']
|
||||||
boolean accepted
|
accepted boolean [not null, note: 'true = opted-in, false = opted-out']
|
||||||
timestamp given_at
|
given_at timestamp [not null, default: `now()`]
|
||||||
inet ip_address
|
ip_address inet [not null, note: 'Proof of consent for CNIL audits']
|
||||||
string user_agent
|
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)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,27 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table data_retention_logs {
|
||||||
DATA_RETENTION_LOGS {
|
id uuid [primary key]
|
||||||
uuid id PK
|
action_type retention_action_enum [not null]
|
||||||
string action_type
|
users_processed int [not null, default: 0, note: 'Nombre total users analysés']
|
||||||
int users_processed
|
users_warned int [not null, default: 0, note: 'Nombre users notifiés (90j/30j/7j)']
|
||||||
int users_warned
|
users_deleted int [not null, default: 0, note: 'Nombre users supprimés effectivement']
|
||||||
int users_deleted
|
details jsonb [note: 'Détails: threshold_date, user_ids_deleted, notifications_sent']
|
||||||
json details
|
executed_at timestamp [not null, default: `now()`, note: 'Timestamp exécution du job cron']
|
||||||
timestamp executed_at
|
execution_duration_ms bigint [not null, note: 'Durée d exécution en millisecondes']
|
||||||
bigint execution_duration_ms
|
|
||||||
|
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']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,25 +4,42 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ DEVICES : "possède"
|
id uuid [primary key]
|
||||||
DEVICES ||--o{ SESSIONS : "a"
|
}
|
||||||
|
|
||||||
DEVICES {
|
Table devices {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
string device_name
|
device_name varchar(255) [note: 'User-defined device name']
|
||||||
string os
|
os varchar(50) [note: 'iOS, Android, Windows, macOS, Linux']
|
||||||
string browser
|
browser varchar(50) [note: 'Safari, Chrome, Firefox, etc.']
|
||||||
string device_type
|
device_type device_type_enum [not null, note: 'mobile, tablet, desktop, car']
|
||||||
boolean is_trusted
|
is_trusted boolean [not null, default: false, note: 'Bypass 2FA for 30 days if true']
|
||||||
timestamp trusted_until
|
trusted_until timestamp [note: 'NULL if not trusted, expires after 30 days']
|
||||||
timestamp first_seen_at
|
first_seen_at timestamp [not null, default: `now()`]
|
||||||
timestamp last_seen_at
|
last_seen_at timestamp [not null, default: `now()`]
|
||||||
inet last_ip
|
last_ip inet [not null]
|
||||||
string last_city
|
last_city varchar(100)
|
||||||
string last_country_code
|
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)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,42 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ DATA_EXPORTS : "demande"
|
id uuid [primary key]
|
||||||
|
}
|
||||||
|
|
||||||
DATA_EXPORTS {
|
Table data_exports {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
string status
|
status export_status_enum [not null, default: 'pending']
|
||||||
string export_url
|
export_url varchar(512) [note: 'S3/CDN signed URL (NULL until generated)']
|
||||||
bigint size_bytes
|
size_bytes bigint [note: 'File size in bytes (NULL until generated)']
|
||||||
string format
|
format export_format_enum [not null, default: 'json']
|
||||||
timestamp requested_at
|
requested_at timestamp [not null, default: `now()`]
|
||||||
timestamp generated_at
|
generated_at timestamp [note: 'When export file was created (NULL if pending/generating)']
|
||||||
timestamp expires_at
|
expires_at timestamp [note: 'Auto-calculated: generated_at + 7 days']
|
||||||
timestamp downloaded_at
|
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)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,17 +4,36 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ INTEREST_GAUGES : "possède"
|
id uuid [primary key]
|
||||||
|
}
|
||||||
|
|
||||||
INTEREST_GAUGES {
|
Table interest_gauges {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
string category
|
category interest_category_enum [not null]
|
||||||
decimal score
|
score decimal(5,2) [not null, default: 0, note: 'Range: 0.00 to 100.00']
|
||||||
timestamp last_updated
|
last_updated timestamp [not null, default: `now()`]
|
||||||
int interactions_count
|
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']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,36 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ LOCATION_HISTORY : "génère"
|
id uuid [primary key]
|
||||||
|
}
|
||||||
|
|
||||||
LOCATION_HISTORY {
|
Table location_history {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
geography location
|
location geography [note: 'PostGIS geography type: POINT with SRID 4326 (WGS84)']
|
||||||
string geohash
|
geohash varchar(12) [note: 'Precision 5 geohash (~5km²) after anonymization']
|
||||||
boolean anonymized
|
anonymized boolean [not null, default: false, note: 'true after 24h auto-anonymization']
|
||||||
string context
|
context location_context_enum [not null]
|
||||||
float speed_kmh
|
speed_kmh float [note: 'GPS speed in km/h (NULL if stationary)']
|
||||||
float accuracy_meters
|
accuracy_meters float [not null, note: 'GPS accuracy radius in meters']
|
||||||
timestamp created_at
|
created_at timestamp [not null, default: `now()`]
|
||||||
timestamp anonymized_at
|
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']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,33 +4,44 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ PARENTAL_CONSENTS : "a"
|
id uuid [primary key]
|
||||||
PARENTAL_CONSENTS ||--|| PARENTAL_CONTROLS : "configure"
|
birthdate date [not null]
|
||||||
|
|
||||||
PARENTAL_CONSENTS {
|
|
||||||
uuid id PK
|
|
||||||
uuid user_id FK "Ado 13-15 ans"
|
|
||||||
string parent_email
|
|
||||||
string validation_token
|
|
||||||
boolean validated
|
|
||||||
timestamp token_expires_at
|
|
||||||
timestamp validated_at
|
|
||||||
inet parent_ip
|
|
||||||
string parent_user_agent
|
|
||||||
timestamp revoked_at
|
|
||||||
string revocation_reason
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PARENTAL_CONTROLS {
|
Table parental_consents {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid parental_consent_id FK
|
user_id uuid [not null, unique, ref: > users.id, note: 'Ado 13-15 ans (1 consent par user max)']
|
||||||
boolean gps_enabled
|
parent_email varchar(255) [not null, note: 'Email du parent pour validation']
|
||||||
boolean messaging_enabled
|
validation_token varchar(64) [unique, note: 'Token de validation envoyé par email (expire 7j)']
|
||||||
boolean content_16plus_enabled
|
validated boolean [not null, default: false, note: 'true après clic parent sur lien email']
|
||||||
json weekly_digest_config
|
token_expires_at timestamp [not null, note: 'validation_token expire après 7 jours']
|
||||||
timestamp updated_at
|
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]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,28 +4,39 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table privacy_policy_versions {
|
||||||
PRIVACY_POLICY_VERSIONS ||--o{ USER_POLICY_ACCEPTANCES : "acceptée par"
|
id uuid [primary key]
|
||||||
USERS ||--o{ USER_POLICY_ACCEPTANCES : "accepte"
|
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()`]
|
||||||
|
|
||||||
PRIVACY_POLICY_VERSIONS {
|
indexes {
|
||||||
uuid id PK
|
(version) [unique]
|
||||||
string version "v1.0, v2.0, etc."
|
(effective_date) [note: 'Order versions chronologically']
|
||||||
text content_markdown
|
}
|
||||||
boolean major_change
|
|
||||||
text changelog
|
|
||||||
timestamp effective_date
|
|
||||||
timestamp created_at
|
|
||||||
}
|
}
|
||||||
|
|
||||||
USER_POLICY_ACCEPTANCES {
|
Table users {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
}
|
||||||
uuid policy_version_id FK
|
|
||||||
boolean accepted
|
Table user_policy_acceptances {
|
||||||
timestamp accepted_at
|
id uuid [primary key]
|
||||||
inet ip_address
|
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']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,25 +4,66 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ REPORTS : "signale"
|
id uuid [primary key]
|
||||||
CONTENTS ||--o{ REPORTS : "reçoit"
|
username varchar(50)
|
||||||
USERS ||--o{ REPORTS : "modère"
|
}
|
||||||
|
|
||||||
REPORTS {
|
Table contents {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid content_id FK
|
title varchar(255)
|
||||||
uuid reporter_id FK
|
user_id uuid [not null]
|
||||||
uuid moderator_id FK
|
}
|
||||||
string category
|
|
||||||
string status
|
Table reports {
|
||||||
text comment
|
id uuid [primary key]
|
||||||
string evidence_url
|
content_id uuid [not null, ref: > contents.id, note: 'Content being reported']
|
||||||
timestamp reported_at
|
reporter_id uuid [not null, ref: > users.id, note: 'User who filed the report']
|
||||||
timestamp reviewed_at
|
moderator_id uuid [ref: > users.id, note: 'Moderator assigned to review (NULL if pending)']
|
||||||
text moderator_notes
|
category report_category_enum [not null]
|
||||||
string action_taken
|
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é)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,42 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ SESSIONS : "possède"
|
id uuid [primary key]
|
||||||
DEVICES ||--o{ SESSIONS : "associé"
|
email varchar(255) [not null, unique]
|
||||||
|
username varchar(50) [not null, unique]
|
||||||
|
}
|
||||||
|
|
||||||
SESSIONS {
|
Table devices {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
uuid device_id FK
|
device_name varchar(255)
|
||||||
string access_token_hash
|
device_type varchar(50)
|
||||||
string refresh_token_hash
|
}
|
||||||
timestamp access_token_expires_at
|
|
||||||
timestamp refresh_token_expires_at
|
Table sessions {
|
||||||
inet ip_address
|
id uuid [primary key]
|
||||||
string user_agent
|
user_id uuid [not null, ref: > users.id]
|
||||||
string city
|
device_id uuid [ref: > devices.id]
|
||||||
string country_code
|
access_token_hash varchar(64) [not null, note: 'SHA256 hash, never stored in clear']
|
||||||
timestamp created_at
|
refresh_token_hash varchar(64) [not null, note: 'SHA256 hash, auto-rotated']
|
||||||
timestamp last_activity_at
|
access_token_expires_at timestamp [not null, note: 'Lifetime: 15 minutes']
|
||||||
timestamp revoked_at
|
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']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,43 @@
|
|||||||
|
|
||||||
## Diagramme
|
## Diagramme
|
||||||
|
|
||||||
```mermaid
|
```kroki-dbml
|
||||||
erDiagram
|
Table users {
|
||||||
USERS ||--o{ USER_PROFILE_HISTORY : "modifie"
|
id uuid [primary key]
|
||||||
|
email varchar(255)
|
||||||
|
username varchar(50)
|
||||||
|
bio text
|
||||||
|
}
|
||||||
|
|
||||||
USER_PROFILE_HISTORY {
|
Table user_profile_history {
|
||||||
uuid id PK
|
id uuid [primary key]
|
||||||
uuid user_id FK
|
user_id uuid [not null, ref: > users.id]
|
||||||
string field_name "email/username/bio/etc."
|
field_name profile_field_enum [not null, note: 'Champ modifié (email, username, bio, etc.)']
|
||||||
text old_value
|
old_value text [note: 'Valeur avant modification (NULL si création)']
|
||||||
text new_value
|
new_value text [not null, note: 'Nouvelle valeur']
|
||||||
string change_reason "user_edit/admin_correction/gdpr_request"
|
change_reason change_reason_enum [not null]
|
||||||
inet ip_address
|
ip_address inet [not null, note: 'IP de l origine du changement']
|
||||||
timestamp changed_at
|
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)']
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
95
docs/examples/dbml-example.md
Normal file
95
docs/examples/dbml-example.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Exemple DBML avec Kroki
|
||||||
|
|
||||||
|
Ce fichier montre comment utiliser DBML dans la documentation MkDocs avec le plugin Kroki.
|
||||||
|
|
||||||
|
## Syntaxe de base
|
||||||
|
|
||||||
|
Pour créer un diagramme de base de données DBML, utilisez un bloc de code avec la balise `kroki-dbml` :
|
||||||
|
|
||||||
|
## Exemple : Schéma utilisateurs et contenus
|
||||||
|
|
||||||
|
```kroki-dbml
|
||||||
|
Table users {
|
||||||
|
id uuid [primary key]
|
||||||
|
email varchar(255) [not null, unique]
|
||||||
|
username varchar(50) [not null, unique]
|
||||||
|
password_hash varchar(255) [not null]
|
||||||
|
created_at timestamp [not null, default: `now()`]
|
||||||
|
updated_at timestamp [not null, default: `now()`]
|
||||||
|
|
||||||
|
indexes {
|
||||||
|
(email) [unique]
|
||||||
|
(username) [unique]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Table contents {
|
||||||
|
id uuid [primary key]
|
||||||
|
user_id uuid [not null, ref: > users.id]
|
||||||
|
title varchar(255) [not null]
|
||||||
|
description text
|
||||||
|
audio_url varchar(512) [not null]
|
||||||
|
location geography(POINT, 4326) [note: 'PostGIS geography type']
|
||||||
|
duration_seconds int [not null]
|
||||||
|
category content_category [not null]
|
||||||
|
status content_status [not null, default: 'draft']
|
||||||
|
created_at timestamp [not null, default: `now()`]
|
||||||
|
published_at timestamp
|
||||||
|
|
||||||
|
indexes {
|
||||||
|
(user_id)
|
||||||
|
(status)
|
||||||
|
(location) [type: gist, note: 'Spatial index']
|
||||||
|
(created_at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Table interest_gauges {
|
||||||
|
id uuid [primary key]
|
||||||
|
user_id uuid [not null, ref: > users.id]
|
||||||
|
category varchar(50) [not null]
|
||||||
|
score decimal(5,2) [not null, default: 0, note: 'Score 0-100']
|
||||||
|
last_updated timestamp [not null, default: `now()`]
|
||||||
|
|
||||||
|
indexes {
|
||||||
|
(user_id, category) [unique]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Enum content_category {
|
||||||
|
"automobile"
|
||||||
|
"travel"
|
||||||
|
"music"
|
||||||
|
"culture"
|
||||||
|
"sport"
|
||||||
|
"education"
|
||||||
|
}
|
||||||
|
|
||||||
|
Enum content_status {
|
||||||
|
"draft"
|
||||||
|
"published"
|
||||||
|
"archived"
|
||||||
|
"moderated"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Avantages de DBML
|
||||||
|
|
||||||
|
- ✅ **Syntaxe claire** : Plus lisible que Mermaid pour les schémas BDD
|
||||||
|
- ✅ **Types PostGIS** : Peut documenter les types spéciaux (geography, geometry)
|
||||||
|
- ✅ **Index et contraintes** : Documentation complète des index et contraintes
|
||||||
|
- ✅ **Relations** : Relations explicites entre tables
|
||||||
|
- ✅ **Enums** : Support natif des types énumérés
|
||||||
|
- ✅ **Notes** : Annotations directement dans le schéma
|
||||||
|
|
||||||
|
## Utilisation dans votre projet
|
||||||
|
|
||||||
|
Pour documenter vos schémas de base de données dans RoadWave :
|
||||||
|
|
||||||
|
1. Créez vos fichiers `.md` dans `docs/domains/<domain>/`
|
||||||
|
2. Ajoutez des blocs `kroki-dbml` pour les schémas
|
||||||
|
3. Le rendu sera automatique lors de `make docs-serve`
|
||||||
|
|
||||||
|
## Référence DBML
|
||||||
|
|
||||||
|
Consultez la [documentation DBML officielle](https://dbml.dbdiagram.io/docs/) pour la syntaxe complète.
|
||||||
@@ -35,6 +35,12 @@ theme:
|
|||||||
plugins:
|
plugins:
|
||||||
- search:
|
- search:
|
||||||
lang: fr
|
lang: fr
|
||||||
|
- kroki:
|
||||||
|
ServerURL: https://kroki.io
|
||||||
|
EnableBlockDiag: true
|
||||||
|
Enablebpmn: true
|
||||||
|
EnableExcalidraw: true
|
||||||
|
EnableMermaid: true
|
||||||
# - glightbox: # Lightbox pour agrandir les images (désactivé temporairement)
|
# - glightbox: # Lightbox pour agrandir les images (désactivé temporairement)
|
||||||
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
|
|||||||
Reference in New Issue
Block a user