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
|
||||
```
|
||||
|
||||
**First run**: The first `make docs-serve` will build a custom Docker image with mkdocs-kroki-plugin. This takes ~30s but is cached for subsequent runs.
|
||||
|
||||
**DBML Support**: The documentation now supports DBML (Database Markup Language) for database diagrams via the Kroki plugin. Use `kroki-dbml` code blocks in markdown files. See [docs/examples/dbml-example.md](docs/examples/dbml-example.md) for examples.
|
||||
|
||||
## Working with sqlc
|
||||
|
||||
When adding or modifying database queries:
|
||||
|
||||
@@ -4,21 +4,36 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o| ACCOUNT_DELETIONS : "demande"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
email varchar(255)
|
||||
status varchar(20)
|
||||
}
|
||||
|
||||
ACCOUNT_DELETIONS {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string status
|
||||
string cancellation_token
|
||||
timestamp requested_at
|
||||
timestamp effective_at "requested_at + 30j"
|
||||
timestamp cancelled_at
|
||||
timestamp deleted_at
|
||||
string deletion_reason
|
||||
json deleted_data_summary
|
||||
Table account_deletions {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, unique, ref: - users.id, note: 'One-to-one: un user ne peut avoir qu une seule demande active']
|
||||
status deletion_status_enum [not null, default: 'pending']
|
||||
cancellation_token varchar(64) [unique, note: 'Token dans email pour annuler (expire après 30j)']
|
||||
requested_at timestamp [not null, default: `now()`]
|
||||
effective_at timestamp [not null, note: 'Auto-calculated: requested_at + 30 days']
|
||||
cancelled_at timestamp [note: 'Timestamp annulation via lien email (NULL si non annulé)']
|
||||
deleted_at timestamp [note: 'Timestamp suppression effective (NULL si pending/cancelled)']
|
||||
deletion_reason text [note: 'Raison optionnelle fournie par l utilisateur']
|
||||
deleted_data_summary jsonb [note: 'Résumé des données supprimées (audit trail)']
|
||||
|
||||
indexes {
|
||||
(user_id) [unique]
|
||||
(status, effective_at) [note: 'Daily cron job: WHERE status = pending AND effective_at < NOW()']
|
||||
(cancellation_token) [unique]
|
||||
}
|
||||
}
|
||||
|
||||
Enum deletion_status_enum {
|
||||
pending [note: 'Grace period actif (30j), compte désactivé, annulation possible']
|
||||
cancelled [note: 'Utilisateur a annulé via lien email']
|
||||
completed [note: 'Suppression effective réalisée après 30j']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,32 +4,56 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
BREACH_INCIDENTS ||--o{ BREACH_AFFECTED_USERS : "impacte"
|
||||
USERS ||--o{ BREACH_AFFECTED_USERS : "est impacté"
|
||||
```kroki-dbml
|
||||
Table breach_incidents {
|
||||
id uuid [primary key]
|
||||
severity breach_severity_enum [not null]
|
||||
description text [not null, note: 'Description détaillée de l incident']
|
||||
data_categories_affected jsonb [not null, note: 'Array: ["gps", "email", "listening_history"]']
|
||||
estimated_users_count int [not null, note: 'Estimation nombre users impactés']
|
||||
detected_at timestamp [not null, default: `now()`, note: 'H+0: Détection initiale']
|
||||
contained_at timestamp [note: 'Timestamp confinement de la faille']
|
||||
cnil_notified_at timestamp [note: 'H+48: Notification CNIL si requis']
|
||||
users_notified_at timestamp [note: 'H+72: Notification users si risque élevé']
|
||||
mitigation_actions text [note: 'Actions correctives mises en place']
|
||||
cnil_notification_required boolean [not null, default: false]
|
||||
user_notification_required boolean [not null, default: false]
|
||||
|
||||
BREACH_INCIDENTS {
|
||||
uuid id PK
|
||||
string severity "low/medium/high/critical"
|
||||
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
|
||||
indexes {
|
||||
(severity, detected_at) [note: 'Incidents par gravité et chronologie']
|
||||
(cnil_notification_required, cnil_notified_at) [note: 'Track CNIL notification compliance']
|
||||
}
|
||||
}
|
||||
|
||||
BREACH_AFFECTED_USERS {
|
||||
uuid id PK
|
||||
uuid breach_id FK
|
||||
uuid user_id FK
|
||||
timestamp notified_at
|
||||
string notification_channel "email/push/sms"
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
Table breach_affected_users {
|
||||
id uuid [primary key]
|
||||
breach_id uuid [not null, ref: > breach_incidents.id]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
notified_at timestamp [note: 'Timestamp notification user (NULL si pas encore notifié)']
|
||||
notification_channel notification_channel_enum [note: 'Canal utilisé pour notifier']
|
||||
|
||||
indexes {
|
||||
(breach_id, user_id) [unique, note: 'Un user ne peut être listé qu une fois par incident']
|
||||
(breach_id, notified_at) [note: 'Track notification progress']
|
||||
(user_id) [note: 'Historique incidents pour un user']
|
||||
}
|
||||
}
|
||||
|
||||
Enum breach_severity_enum {
|
||||
low [note: 'Pas de notification requise (mesures techniques suffisantes)']
|
||||
medium [note: 'Notification CNIL uniquement']
|
||||
high [note: 'Notification CNIL + utilisateurs']
|
||||
critical [note: 'Notification immédiate tous canaux + SMS fondateur']
|
||||
}
|
||||
|
||||
Enum notification_channel_enum {
|
||||
email [note: 'Email notification']
|
||||
push [note: 'Push notification mobile']
|
||||
sms [note: 'SMS (critical only)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,19 +4,32 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ USER_CONSENTS : "donne"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
USER_CONSENTS {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string consent_type
|
||||
string consent_version
|
||||
boolean accepted
|
||||
timestamp given_at
|
||||
inet ip_address
|
||||
string user_agent
|
||||
Table user_consents {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
consent_type consent_type_enum [not null]
|
||||
consent_version varchar(10) [not null, note: 'Format: v1.0, v2.0, etc.']
|
||||
accepted boolean [not null, note: 'true = opted-in, false = opted-out']
|
||||
given_at timestamp [not null, default: `now()`]
|
||||
ip_address inet [not null, note: 'Proof of consent for CNIL audits']
|
||||
user_agent text [not null, note: 'Device/browser proof']
|
||||
|
||||
indexes {
|
||||
(user_id, consent_type, consent_version) [note: 'Latest consent per type']
|
||||
(user_id, given_at) [note: 'Consent history timeline']
|
||||
}
|
||||
}
|
||||
|
||||
Enum consent_type_enum {
|
||||
geolocation_precise [note: 'Géolocalisation GPS précise (obligatoire pour contenu hyperlocal)']
|
||||
analytics [note: 'Analytics Matomo (optionnel)']
|
||||
push_notifications [note: 'Notifications push (optionnel)']
|
||||
cookies_analytics [note: 'Cookies analytiques (optionnel)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,17 +4,27 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
DATA_RETENTION_LOGS {
|
||||
uuid id PK
|
||||
string action_type
|
||||
int users_processed
|
||||
int users_warned
|
||||
int users_deleted
|
||||
json details
|
||||
timestamp executed_at
|
||||
bigint execution_duration_ms
|
||||
```kroki-dbml
|
||||
Table data_retention_logs {
|
||||
id uuid [primary key]
|
||||
action_type retention_action_enum [not null]
|
||||
users_processed int [not null, default: 0, note: 'Nombre total users analysés']
|
||||
users_warned int [not null, default: 0, note: 'Nombre users notifiés (90j/30j/7j)']
|
||||
users_deleted int [not null, default: 0, note: 'Nombre users supprimés effectivement']
|
||||
details jsonb [note: 'Détails: threshold_date, user_ids_deleted, notifications_sent']
|
||||
executed_at timestamp [not null, default: `now()`, note: 'Timestamp exécution du job cron']
|
||||
execution_duration_ms bigint [not null, note: 'Durée d exécution en millisecondes']
|
||||
|
||||
indexes {
|
||||
(action_type, executed_at) [note: 'Historique jobs par type']
|
||||
(executed_at) [note: 'Timeline complète des jobs']
|
||||
}
|
||||
}
|
||||
|
||||
Enum retention_action_enum {
|
||||
check_inactive [note: 'Vérification quotidienne comptes inactifs > 5 ans']
|
||||
send_warnings [note: 'Envoi notifications (90j/30j/7j avant suppression)']
|
||||
delete_accounts [note: 'Suppression effective comptes inactifs']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,25 +4,42 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ DEVICES : "possède"
|
||||
DEVICES ||--o{ SESSIONS : "a"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
DEVICES {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string device_name
|
||||
string os
|
||||
string browser
|
||||
string device_type
|
||||
boolean is_trusted
|
||||
timestamp trusted_until
|
||||
timestamp first_seen_at
|
||||
timestamp last_seen_at
|
||||
inet last_ip
|
||||
string last_city
|
||||
string last_country_code
|
||||
Table devices {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
device_name varchar(255) [note: 'User-defined device name']
|
||||
os varchar(50) [note: 'iOS, Android, Windows, macOS, Linux']
|
||||
browser varchar(50) [note: 'Safari, Chrome, Firefox, etc.']
|
||||
device_type device_type_enum [not null, note: 'mobile, tablet, desktop, car']
|
||||
is_trusted boolean [not null, default: false, note: 'Bypass 2FA for 30 days if true']
|
||||
trusted_until timestamp [note: 'NULL if not trusted, expires after 30 days']
|
||||
first_seen_at timestamp [not null, default: `now()`]
|
||||
last_seen_at timestamp [not null, default: `now()`]
|
||||
last_ip inet [not null]
|
||||
last_city varchar(100)
|
||||
last_country_code char(2)
|
||||
|
||||
indexes {
|
||||
(user_id, last_seen_at) [note: 'List user devices by recent activity']
|
||||
(user_id, is_trusted) [note: 'Find trusted devices for user']
|
||||
}
|
||||
}
|
||||
|
||||
Table sessions {
|
||||
id uuid [primary key]
|
||||
device_id uuid [ref: > devices.id]
|
||||
}
|
||||
|
||||
Enum device_type_enum {
|
||||
mobile [note: 'Smartphone Android/iOS']
|
||||
tablet [note: 'Tablette']
|
||||
desktop [note: 'Ordinateur']
|
||||
car [note: 'Système embarqué (CarPlay/Android Auto)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,21 +4,42 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ DATA_EXPORTS : "demande"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
DATA_EXPORTS {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string status
|
||||
string export_url
|
||||
bigint size_bytes
|
||||
string format
|
||||
timestamp requested_at
|
||||
timestamp generated_at
|
||||
timestamp expires_at
|
||||
timestamp downloaded_at
|
||||
Table data_exports {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
status export_status_enum [not null, default: 'pending']
|
||||
export_url varchar(512) [note: 'S3/CDN signed URL (NULL until generated)']
|
||||
size_bytes bigint [note: 'File size in bytes (NULL until generated)']
|
||||
format export_format_enum [not null, default: 'json']
|
||||
requested_at timestamp [not null, default: `now()`]
|
||||
generated_at timestamp [note: 'When export file was created (NULL if pending/generating)']
|
||||
expires_at timestamp [note: 'Auto-calculated: generated_at + 7 days']
|
||||
downloaded_at timestamp [note: 'First download timestamp (NULL if not yet downloaded)']
|
||||
|
||||
indexes {
|
||||
(user_id, requested_at) [note: 'User export history']
|
||||
(status, requested_at) [note: 'Background worker queue (WHERE status = pending)']
|
||||
(expires_at) [note: 'Daily cleanup job (DELETE WHERE expires_at < NOW())']
|
||||
}
|
||||
}
|
||||
|
||||
Enum export_status_enum {
|
||||
pending [note: 'Demande en file d attente']
|
||||
generating [note: 'Génération en cours (worker background)']
|
||||
ready [note: 'Export disponible au téléchargement']
|
||||
downloaded [note: 'Export téléchargé par l utilisateur']
|
||||
expired [note: 'Export expiré (supprimé automatiquement)']
|
||||
}
|
||||
|
||||
Enum export_format_enum {
|
||||
json [note: 'Machine-readable (données brutes)']
|
||||
html [note: 'Human-readable (page web stylée)']
|
||||
zip [note: 'Archive complète (JSON + HTML + audio files)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,17 +4,36 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ INTEREST_GAUGES : "possède"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
INTEREST_GAUGES {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string category
|
||||
decimal score
|
||||
timestamp last_updated
|
||||
int interactions_count
|
||||
Table interest_gauges {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
category interest_category_enum [not null]
|
||||
score decimal(5,2) [not null, default: 0, note: 'Range: 0.00 to 100.00']
|
||||
last_updated timestamp [not null, default: `now()`]
|
||||
interactions_count int [not null, default: 0, note: 'Total interactions for this category']
|
||||
|
||||
indexes {
|
||||
(user_id, category) [unique, note: 'One gauge per user per category']
|
||||
(user_id, score) [note: 'Order categories by score for recommendations']
|
||||
}
|
||||
}
|
||||
|
||||
Enum interest_category_enum {
|
||||
automobile [note: 'Voitures, mécanique, course automobile']
|
||||
travel [note: 'Voyages, tourisme, découverte']
|
||||
music [note: 'Musique, concerts, artistes']
|
||||
news [note: 'Actualités, politique, économie']
|
||||
sport [note: 'Sports, événements sportifs']
|
||||
culture [note: 'Cinéma, livres, expositions']
|
||||
food [note: 'Gastronomie, restaurants, recettes']
|
||||
tech [note: 'Technologie, innovation, gadgets']
|
||||
history [note: 'Histoire, patrimoine, musées']
|
||||
nature [note: 'Nature, randonnée, écologie']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,21 +4,36 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ LOCATION_HISTORY : "génère"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
LOCATION_HISTORY {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
geography location
|
||||
string geohash
|
||||
boolean anonymized
|
||||
string context
|
||||
float speed_kmh
|
||||
float accuracy_meters
|
||||
timestamp created_at
|
||||
timestamp anonymized_at
|
||||
Table location_history {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
location geography [note: 'PostGIS geography type: POINT with SRID 4326 (WGS84)']
|
||||
geohash varchar(12) [note: 'Precision 5 geohash (~5km²) after anonymization']
|
||||
anonymized boolean [not null, default: false, note: 'true after 24h auto-anonymization']
|
||||
context location_context_enum [not null]
|
||||
speed_kmh float [note: 'GPS speed in km/h (NULL if stationary)']
|
||||
accuracy_meters float [not null, note: 'GPS accuracy radius in meters']
|
||||
created_at timestamp [not null, default: `now()`]
|
||||
anonymized_at timestamp [note: 'When precise location was replaced by geohash']
|
||||
|
||||
indexes {
|
||||
(user_id, created_at) [note: 'User location timeline']
|
||||
(created_at, anonymized) [note: 'Daily anonymization job (WHERE anonymized = false AND created_at < NOW() - 24h)']
|
||||
(location) [type: gist, note: 'PostGIS spatial index for proximity queries']
|
||||
(geohash) [note: 'Analytics queries on anonymized data']
|
||||
}
|
||||
}
|
||||
|
||||
Enum location_context_enum {
|
||||
listening [note: 'Position pendant écoute de contenu']
|
||||
search [note: 'Position lors d une recherche']
|
||||
background [note: 'Tracking en arrière-plan']
|
||||
manual [note: 'Position partagée manuellement']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,33 +4,44 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ PARENTAL_CONSENTS : "a"
|
||||
PARENTAL_CONSENTS ||--|| PARENTAL_CONTROLS : "configure"
|
||||
|
||||
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
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
birthdate date [not null]
|
||||
}
|
||||
|
||||
PARENTAL_CONTROLS {
|
||||
uuid id PK
|
||||
uuid parental_consent_id FK
|
||||
boolean gps_enabled
|
||||
boolean messaging_enabled
|
||||
boolean content_16plus_enabled
|
||||
json weekly_digest_config
|
||||
timestamp updated_at
|
||||
Table parental_consents {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, unique, ref: > users.id, note: 'Ado 13-15 ans (1 consent par user max)']
|
||||
parent_email varchar(255) [not null, note: 'Email du parent pour validation']
|
||||
validation_token varchar(64) [unique, note: 'Token de validation envoyé par email (expire 7j)']
|
||||
validated boolean [not null, default: false, note: 'true après clic parent sur lien email']
|
||||
token_expires_at timestamp [not null, note: 'validation_token expire après 7 jours']
|
||||
validated_at timestamp [note: 'Timestamp de validation parent (NULL si non validé)']
|
||||
parent_ip inet [note: 'IP du parent lors de la validation']
|
||||
parent_user_agent text [note: 'User agent parent (preuve validation)']
|
||||
revoked_at timestamp [note: 'Révocation du consentement parental']
|
||||
revocation_reason text [note: 'Raison de la révocation (optionnel)']
|
||||
|
||||
indexes {
|
||||
(user_id) [unique, note: 'Un seul consentement parental actif par user']
|
||||
(validation_token) [unique, note: 'Lookup rapide pour validation lien email']
|
||||
(validated, token_expires_at) [note: 'Cleanup des tokens expirés non validés']
|
||||
}
|
||||
}
|
||||
|
||||
Table parental_controls {
|
||||
id uuid [primary key]
|
||||
parental_consent_id uuid [not null, unique, ref: - parental_consents.id, note: 'One-to-one relationship']
|
||||
gps_enabled boolean [not null, default: false, note: 'Autoriser GPS précis (false = GeoIP uniquement)']
|
||||
messaging_enabled boolean [not null, default: false, note: 'Autoriser messagerie privée']
|
||||
content_16plus_enabled boolean [not null, default: false, note: 'Autoriser contenu 16+']
|
||||
weekly_digest_config jsonb [note: 'Config notifications hebdo parent (email, contenu, format)']
|
||||
updated_at timestamp [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
(parental_consent_id) [unique]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,28 +4,39 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
PRIVACY_POLICY_VERSIONS ||--o{ USER_POLICY_ACCEPTANCES : "acceptée par"
|
||||
USERS ||--o{ USER_POLICY_ACCEPTANCES : "accepte"
|
||||
```kroki-dbml
|
||||
Table privacy_policy_versions {
|
||||
id uuid [primary key]
|
||||
version varchar(10) [not null, unique, note: 'Format: v1.0, v2.0, etc.']
|
||||
content_markdown text [not null, note: 'Source: docs/legal/politique-confidentialite.md (versionné Git)']
|
||||
major_change boolean [not null, default: false, note: 'true = popup obligatoire pour tous les users']
|
||||
changelog text [not null, note: 'Résumé des changements pour communication']
|
||||
effective_date timestamp [not null, note: 'Date d entrée en vigueur de cette version']
|
||||
created_at timestamp [not null, default: `now()`]
|
||||
|
||||
PRIVACY_POLICY_VERSIONS {
|
||||
uuid id PK
|
||||
string version "v1.0, v2.0, etc."
|
||||
text content_markdown
|
||||
boolean major_change
|
||||
text changelog
|
||||
timestamp effective_date
|
||||
timestamp created_at
|
||||
indexes {
|
||||
(version) [unique]
|
||||
(effective_date) [note: 'Order versions chronologically']
|
||||
}
|
||||
}
|
||||
|
||||
USER_POLICY_ACCEPTANCES {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
uuid policy_version_id FK
|
||||
boolean accepted
|
||||
timestamp accepted_at
|
||||
inet ip_address
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
}
|
||||
|
||||
Table user_policy_acceptances {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
policy_version_id uuid [not null, ref: > privacy_policy_versions.id]
|
||||
accepted boolean [not null, note: 'true = accepté, false = refusé (compte gelé)']
|
||||
accepted_at timestamp [not null, default: `now()`]
|
||||
ip_address inet [not null, note: 'IP de l utilisateur lors de l acceptation (preuve CNIL)']
|
||||
|
||||
indexes {
|
||||
(user_id, policy_version_id) [unique, note: 'Un user ne peut accepter qu une fois une version']
|
||||
(user_id, accepted_at) [note: 'Historique acceptations user']
|
||||
(policy_version_id) [note: 'Count acceptances par version']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,25 +4,66 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ REPORTS : "signale"
|
||||
CONTENTS ||--o{ REPORTS : "reçoit"
|
||||
USERS ||--o{ REPORTS : "modère"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
username varchar(50)
|
||||
}
|
||||
|
||||
REPORTS {
|
||||
uuid id PK
|
||||
uuid content_id FK
|
||||
uuid reporter_id FK
|
||||
uuid moderator_id FK
|
||||
string category
|
||||
string status
|
||||
text comment
|
||||
string evidence_url
|
||||
timestamp reported_at
|
||||
timestamp reviewed_at
|
||||
text moderator_notes
|
||||
string action_taken
|
||||
Table contents {
|
||||
id uuid [primary key]
|
||||
title varchar(255)
|
||||
user_id uuid [not null]
|
||||
}
|
||||
|
||||
Table reports {
|
||||
id uuid [primary key]
|
||||
content_id uuid [not null, ref: > contents.id, note: 'Content being reported']
|
||||
reporter_id uuid [not null, ref: > users.id, note: 'User who filed the report']
|
||||
moderator_id uuid [ref: > users.id, note: 'Moderator assigned to review (NULL if pending)']
|
||||
category report_category_enum [not null]
|
||||
status report_status_enum [not null, default: 'pending']
|
||||
comment text [note: 'Reporter explanation (mandatory for "other" category)']
|
||||
evidence_url varchar(512) [note: 'Screenshot or additional proof URL']
|
||||
reported_at timestamp [not null, default: `now()`]
|
||||
reviewed_at timestamp [note: 'When moderator reviewed the report']
|
||||
moderator_notes text [note: 'Internal moderator notes']
|
||||
action_taken report_action_enum [note: 'Action decided by moderator']
|
||||
|
||||
indexes {
|
||||
(content_id, status) [note: 'Find all reports for a content']
|
||||
(status, reported_at) [note: 'Queue for moderators (pending first)']
|
||||
(reporter_id) [note: 'User report history (detect abuse)']
|
||||
(moderator_id) [note: 'Reports assigned to moderator']
|
||||
}
|
||||
}
|
||||
|
||||
Enum report_category_enum {
|
||||
spam [note: 'Contenu publicitaire non sollicité']
|
||||
hate_speech [note: 'Discours haineux, discrimination']
|
||||
violence [note: 'Violence explicite']
|
||||
sexual_content [note: 'Contenu sexuel inapproprié']
|
||||
misinformation [note: 'Désinformation, fake news']
|
||||
copyright [note: 'Violation de droits d auteur']
|
||||
wrong_age_rating [note: 'Classification d âge incorrecte']
|
||||
other [note: 'Autre raison (commentaire obligatoire)']
|
||||
}
|
||||
|
||||
Enum report_status_enum {
|
||||
pending [note: 'En attente de revue']
|
||||
under_review [note: 'En cours d examen par modérateur']
|
||||
actioned [note: 'Action prise (contenu retiré/édité)']
|
||||
dismissed [note: 'Signalement rejeté (contenu valide)']
|
||||
duplicate [note: 'Doublon d un signalement existant']
|
||||
}
|
||||
|
||||
Enum report_action_enum {
|
||||
content_removed [note: 'Contenu supprimé']
|
||||
content_edited [note: 'Métadonnées modifiées (âge, tags)']
|
||||
warning_sent [note: 'Avertissement au créateur']
|
||||
strike_issued [note: 'Strike ajouté au créateur']
|
||||
account_suspended [note: 'Compte créateur suspendu']
|
||||
no_action [note: 'Aucune action (signalement infondé)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,26 +4,42 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ SESSIONS : "possède"
|
||||
DEVICES ||--o{ SESSIONS : "associé"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
email varchar(255) [not null, unique]
|
||||
username varchar(50) [not null, unique]
|
||||
}
|
||||
|
||||
SESSIONS {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
uuid device_id FK
|
||||
string access_token_hash
|
||||
string refresh_token_hash
|
||||
timestamp access_token_expires_at
|
||||
timestamp refresh_token_expires_at
|
||||
inet ip_address
|
||||
string user_agent
|
||||
string city
|
||||
string country_code
|
||||
timestamp created_at
|
||||
timestamp last_activity_at
|
||||
timestamp revoked_at
|
||||
Table devices {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
device_name varchar(255)
|
||||
device_type varchar(50)
|
||||
}
|
||||
|
||||
Table sessions {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
device_id uuid [ref: > devices.id]
|
||||
access_token_hash varchar(64) [not null, note: 'SHA256 hash, never stored in clear']
|
||||
refresh_token_hash varchar(64) [not null, note: 'SHA256 hash, auto-rotated']
|
||||
access_token_expires_at timestamp [not null, note: 'Lifetime: 15 minutes']
|
||||
refresh_token_expires_at timestamp [not null, note: 'Lifetime: 30 days (rolling)']
|
||||
ip_address inet [not null]
|
||||
user_agent text [not null]
|
||||
city varchar(100)
|
||||
country_code char(2)
|
||||
created_at timestamp [not null, default: `now()`]
|
||||
last_activity_at timestamp [not null, default: `now()`]
|
||||
revoked_at timestamp [note: 'NULL if active, timestamp if manually revoked']
|
||||
|
||||
indexes {
|
||||
(user_id, revoked_at) [note: 'Find active sessions for user']
|
||||
(device_id)
|
||||
(refresh_token_hash) [unique, note: 'Detect replay attacks']
|
||||
(last_activity_at) [note: 'Auto-cleanup inactive sessions']
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,19 +4,43 @@
|
||||
|
||||
## Diagramme
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
USERS ||--o{ USER_PROFILE_HISTORY : "modifie"
|
||||
```kroki-dbml
|
||||
Table users {
|
||||
id uuid [primary key]
|
||||
email varchar(255)
|
||||
username varchar(50)
|
||||
bio text
|
||||
}
|
||||
|
||||
USER_PROFILE_HISTORY {
|
||||
uuid id PK
|
||||
uuid user_id FK
|
||||
string field_name "email/username/bio/etc."
|
||||
text old_value
|
||||
text new_value
|
||||
string change_reason "user_edit/admin_correction/gdpr_request"
|
||||
inet ip_address
|
||||
timestamp changed_at
|
||||
Table user_profile_history {
|
||||
id uuid [primary key]
|
||||
user_id uuid [not null, ref: > users.id]
|
||||
field_name profile_field_enum [not null, note: 'Champ modifié (email, username, bio, etc.)']
|
||||
old_value text [note: 'Valeur avant modification (NULL si création)']
|
||||
new_value text [not null, note: 'Nouvelle valeur']
|
||||
change_reason change_reason_enum [not null]
|
||||
ip_address inet [not null, note: 'IP de l origine du changement']
|
||||
changed_at timestamp [not null, default: `now()`]
|
||||
|
||||
indexes {
|
||||
(user_id, changed_at) [note: 'Timeline modifications user (ordre chronologique)']
|
||||
(field_name, changed_at) [note: 'Track modifications par type de champ']
|
||||
(user_id, field_name) [note: 'Historique d un champ spécifique']
|
||||
}
|
||||
}
|
||||
|
||||
Enum profile_field_enum {
|
||||
email [note: 'Re-vérification requise après changement']
|
||||
username [note: 'Limite: 1 changement/30j']
|
||||
bio [note: 'Biographie utilisateur']
|
||||
avatar_url [note: 'URL de l avatar']
|
||||
date_of_birth [note: 'Date de naissance']
|
||||
}
|
||||
|
||||
Enum change_reason_enum {
|
||||
user_edit [note: 'Modification self-service utilisateur']
|
||||
admin_correction [note: 'Correction par admin (via backoffice)']
|
||||
gdpr_request [note: 'Suite demande RGPD formelle (droit de rectification)']
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
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:
|
||||
- search:
|
||||
lang: fr
|
||||
- kroki:
|
||||
ServerURL: https://kroki.io
|
||||
EnableBlockDiag: true
|
||||
Enablebpmn: true
|
||||
EnableExcalidraw: true
|
||||
EnableMermaid: true
|
||||
# - glightbox: # Lightbox pour agrandir les images (désactivé temporairement)
|
||||
|
||||
markdown_extensions:
|
||||
|
||||
Reference in New Issue
Block a user