talas-group/12_DOCUMENTATION/Imprimables/Intercalaire_03.html
senke 1db6d066c0 nettoyage repo : réorganisation fichiers en vrac, ajout body solidworks + studio mic ref
- Body SolidWorks v1 → 02_PRODUITS_PHYSIQUES/Microphone/Conception/
- Studio Mic KiCAD (DIYPerks) → 02_PRODUITS_PHYSIQUES/R&D_References/DIY/
- cleanup_ports.sh → 04_INFRA_DEPLOIEMENT/
- mockup_jeu_ux → 11_RECHERCHE_&_LAB/
- Printables → 12_DOCUMENTATION/Imprimables/
- Screenshots, ideas, one.html → _BROUILLON/
- all-talas (23Go) → 13_ARCHIVES/
- Supprimé all-talas.zip (20Go doublon), lock files LibreOffice
- Nettoyé .gitignore
- Remote → Forgejo (10.0.20.105:3000)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 16:31:26 +02:00

9391 lines
181 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Intercalaire 3 - APPS & SERVICES</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; max-width: 900px; margin: 0 auto; padding: 20px; color: #333; }
h1 { border-bottom: 2px solid #2c3e50; padding-bottom: 10px; color: #2c3e50; font-size: 2.5em; }
h2 { color: #34495e; margin-top: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 5px; }
h3 { color: #7f8c8d; }
pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; font-size: 0.9em; border: 1px solid #e9ecef; }
code { font-family: Consolas, Monaco, monospace; background: #f8f9fa; padding: 2px 4px; border-radius: 3px; }
table { border-collapse: collapse; width: 100%; margin-bottom: 1.5em; }
th, td { border: 1px solid #dee2e6; padding: 10px; text-align: left; }
th { background-color: #f8f9fa; font-weight: bold; }
.doc-header { background-color: #ecf0f1; padding: 15px; border-radius: 5px; margin-bottom: 20px; margin-top: 40px; border-left: 5px solid #3498db; }
.doc-header h2 { margin: 0; border: none; padding: 0; color: #2980b9; }
.doc-path { font-family: monospace; font-size: 0.9em; color: #7f8c8d; }
@media print {
body { max-width: 100%; padding: 0; margin: 1cm; font-size: 11pt; }
.page-break { page-break-before: always; }
pre, code { white-space: pre-wrap; word-wrap: break-word; font-size: 10pt; border: none; background: transparent; }
a { text-decoration: none; color: black; }
h1 { font-size: 24pt; }
h2 { font-size: 18pt; }
h3 { font-size: 14pt; }
.doc-header { border: 1px solid #ccc; background-color: transparent; }
}
</style>
</head>
<body>
<h1>Intercalaire 3 : APPS & SERVICES</h1>
<p><em>Généré le 4 Avril 2026 - Projet Talas</em></p>
<hr>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/APIs_&_Rust_Modules/README.md</div>
</div>
<h1>APIs &amp; Modules Rust Performances &amp; Interop</h1>
<p>Ce dossier contient les spécifications API de Talas (REST/gRPC), ainsi que les <strong>modules Rust haute performance</strong> pour le streaming audio, le traitement en ligne, et le rendu.</p>
<h2>Objectifs :</h2>
<ul>
<li>Créer une interopérabilité robuste entre les différents services.</li>
<li>Offrir un moteur Rust performant pour les traitements audio.</li>
<li>Structurer une API claire, documentée, et évolutive.</li>
</ul>
<h2>Contenu :</h2>
<ul>
<li><code>API_Specs/</code> : OpenAPI, gRPC Protobuf, GraphQL schemas</li>
<li><code>Rust_Engines/</code> : streaming, encodage, plugins</li>
<li><code>Benchmarks/</code> : perfs sur FLAC, Opus, WebRTC</li>
</ul>
<blockquote>
<p>Ces modules peuvent être exposés comme services REST ou microservices via gRPC.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 ROUTES_API.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/APIs_&_Rust_Modules/ROUTES_API.md</div>
</div>
<h1>Référence Complète des Routes API Veza</h1>
<blockquote>
<p>Inventaire exhaustif de tous les endpoints REST et WebSocket.
Source : <code>veza-backend-api/internal/api/routes_*.go</code> et <code>veza-stream-server/src/routes/api.rs</code>
~500+ endpoints au total.</p>
</blockquote>
<h2>Conventions</h2>
<ul>
<li><strong>Base URL</strong> : <code>/api/v1</code></li>
<li><strong>Auth</strong> : JWT RS256 via cookie HTTP-only ou header <code>Authorization: Bearer &lt;token&gt;</code></li>
<li><strong>CSRF</strong> : Token Redis obligatoire sur tous les POST/PUT/DELETE protégés</li>
<li><strong>Rate limiting</strong> : Global (1000 req/s), par IP (100 req/s), par endpoint (variable)</li>
<li><strong>Réponses</strong> : JSON, pagination par <code>?page=N&amp;limit=N</code></li>
</ul>
<h3>Légende des badges</h3>
<table>
<thead>
<tr>
<th>Badge</th>
<th>Signification</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓</td>
<td>Public (pas d'auth)</td>
</tr>
<tr>
<td>🔒</td>
<td>Auth requise</td>
</tr>
<tr>
<td>🔒👑</td>
<td>Auth + admin + MFA</td>
</tr>
<tr>
<td>🔒🎨</td>
<td>Auth + rôle content_creator</td>
</tr>
<tr>
<td></td>
<td>Rate limité spécifiquement</td>
</tr>
</tbody>
</table>
<hr />
<h2>1. Authentification (<code>/auth</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_auth.go</code></p>
<h3>Endpoints publics</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 POST</td>
<td><code>/auth/register</code></td>
<td>Inscription</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/login</code></td>
<td>Connexion</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/login/2fa</code></td>
<td>Connexion avec 2FA</td>
<td></td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/refresh</code></td>
<td>Rafraîchir le token</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/verify-email</code></td>
<td>Vérification email</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/resend-verification</code></td>
<td>Renvoyer email vérification</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/auth/check-username</code></td>
<td>Disponibilité pseudo</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/password/reset-request</code></td>
<td>Demande reset mot de passe</td>
<td>⚡ Rate limité</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/auth/password/reset</code></td>
<td>Reset mot de passe</td>
<td></td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/auth/oauth/providers</code></td>
<td>Liste des providers OAuth</td>
<td></td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/auth/oauth/:provider</code></td>
<td>Initier OAuth</td>
<td></td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/auth/oauth/:provider/callback</code></td>
<td>Callback OAuth</td>
<td></td>
</tr>
</tbody>
</table>
<h3>Endpoints protégés</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/auth/logout</code></td>
<td>Déconnexion</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/auth/me</code></td>
<td>Info utilisateur courant</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/stream-token</code></td>
<td>Générer token streaming</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/2fa/setup</code></td>
<td>Configurer 2FA</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/2fa/verify</code></td>
<td>Vérifier 2FA</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/2fa/disable</code></td>
<td>Désactiver 2FA</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/auth/2fa/status</code></td>
<td>Statut 2FA</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/auth/passkeys</code></td>
<td>Lister Passkeys/WebAuthn</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/passkeys/register/begin</code></td>
<td>Début enregistrement passkey</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/auth/passkeys/register/finish</code></td>
<td>Fin enregistrement passkey</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/auth/passkeys/:id</code></td>
<td>Renommer passkey</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/auth/passkeys/:id</code></td>
<td>Supprimer passkey</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/auth/login-history</code></td>
<td>Historique connexions</td>
</tr>
</tbody>
</table>
<hr />
<h2>2. Utilisateurs (<code>/users</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_users.go</code></p>
<h3>Endpoints publics</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/users</code></td>
<td>Lister les utilisateurs</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/users/:id</code></td>
<td>Profil utilisateur</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/users/by-username/:username</code></td>
<td>Profil par pseudo</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/users/search</code></td>
<td>Recherche utilisateurs</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/users/:id/reposts</code></td>
<td>Reposts de l'utilisateur</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/users/:id/gear</code></td>
<td>Profil équipement public</td>
</tr>
</tbody>
</table>
<h3>Endpoints protégés</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/users/suggestions</code></td>
<td>Suggestions de follow</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/settings</code></td>
<td>Paramètres utilisateur</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/users/settings</code></td>
<td>Modifier paramètres</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/users/:id</code></td>
<td>Modifier profil (ownership)</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/:id</code></td>
<td>Supprimer utilisateur (ownership)</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/:id/completion</code></td>
<td>Complétion profil</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/users/me/presence</code></td>
<td>Mettre à jour présence</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/:id/presence</code></td>
<td>Voir la présence</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/:id/follow</code></td>
<td>Suivre</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/:id/follow</code></td>
<td>Ne plus suivre</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/:id/block</code></td>
<td>Bloquer</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/:id/block</code></td>
<td>Débloquer</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/:id/roles</code></td>
<td>Assigner rôle</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/:id/roles/:roleId</code></td>
<td>Révoquer rôle</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/:id/avatar</code></td>
<td>Upload avatar</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/:id/avatar</code></td>
<td>Supprimer avatar</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/:id/likes</code></td>
<td>Pistes likées</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/users/me/password</code></td>
<td>Changer mot de passe</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/me/export</code></td>
<td>Export JSON (sync, fallback)</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/me/export</code></td>
<td>Export RGPD (async)</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/me/exports</code></td>
<td>Lister exports</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/me/exports/:id/download</code></td>
<td>Télécharger export</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/users/me/exports/:id</code></td>
<td>Statut export</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/users/me</code></td>
<td>Supprimer compte</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/users/me/privacy/opt-out</code></td>
<td>CCPA Do Not Sell</td>
</tr>
</tbody>
</table>
<hr />
<h2>3. Rôles (<code>/roles</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_users.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/roles</code></td>
<td>Lister les rôles</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/roles/:id</code></td>
<td>Détail d'un rôle</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/roles</code></td>
<td>Créer un rôle</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/roles/:id</code></td>
<td>Modifier un rôle</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/roles/:id</code></td>
<td>Supprimer un rôle</td>
</tr>
</tbody>
</table>
<hr />
<h2>4. Pistes audio (<code>/tracks</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_tracks.go</code></p>
<h3>Endpoints publics</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/tracks</code></td>
<td>Lister les pistes (auth optionnelle)</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/search</code></td>
<td>Recherche de pistes</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/suggested-tags</code></td>
<td>Suggestions de tags</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id</code></td>
<td>Détail piste (auth optionnelle)</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/lyrics</code></td>
<td>Paroles</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/stats</code></td>
<td>Statistiques</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/waveform</code></td>
<td>Forme d'onde</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/history</code></td>
<td>Historique</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/download</code></td>
<td>Téléchargement</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/shared/:token</code></td>
<td>Piste partagée</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/repost</code></td>
<td>Statut repost (auth optionnelle)</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/comments</code></td>
<td>Commentaires</td>
</tr>
</tbody>
</table>
<h3>Endpoints HLS (publics)</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/hls/info</code></td>
<td>Info stream HLS</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/hls/status</code></td>
<td>Statut stream HLS</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/hls/master.m3u8</code></td>
<td>Playlist master HLS</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/hls/:bitrate/playlist.m3u8</code></td>
<td>Playlist qualité</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/tracks/:id/hls/:bitrate/:segment</code></td>
<td>Segment HLS</td>
</tr>
</tbody>
</table>
<h3>Endpoints protégés</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒🎨 POST</td>
<td><code>/tracks</code></td>
<td>Upload piste</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/recommendations</code></td>
<td>Recommandations</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/tracks/:id</code></td>
<td>Modifier piste (ownership)</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/tracks/:id/lyrics</code></td>
<td>Modifier paroles (ownership)</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/tracks/:id</code></td>
<td>Supprimer piste (ownership)</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/:id/status</code></td>
<td>Statut upload</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/initiate</code></td>
<td>Initier upload chunked</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/chunk</code></td>
<td>Upload chunk</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/complete</code></td>
<td>Finaliser upload chunked</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/quota/:id</code></td>
<td>Quota upload</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/resume/:uploadId</code></td>
<td>Reprendre upload</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/batch/delete</code></td>
<td>Suppression batch</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/batch/update</code></td>
<td>Mise à jour batch</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/like</code></td>
<td>Liker</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/tracks/:id/like</code></td>
<td>Unliker</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/:id/likes</code></td>
<td>Likes de la piste</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/repost</code></td>
<td>Reposter</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/tracks/:id/repost</code></td>
<td>Annuler repost</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/share</code></td>
<td>Créer partage</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/tracks/share/:id</code></td>
<td>Révoquer partage</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/versions/:versionId/restore</code></td>
<td>Restaurer version</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/play</code></td>
<td>Enregistrer lecture</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/stems</code></td>
<td>Upload stem</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/:id/stems</code></td>
<td>Lister stems</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/:id/stems/:name/download</code></td>
<td>Télécharger stem</td>
</tr>
</tbody>
</table>
<h3>Commentaires</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/tracks/:id/comments</code></td>
<td>Créer commentaire</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/comments/:id</code></td>
<td>Supprimer commentaire</td>
</tr>
</tbody>
</table>
<hr />
<h2>5. Playlists (<code>/playlists</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_playlists.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/playlists/shared/:token</code></td>
<td>Playlist partagée</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists</code></td>
<td>Mes playlists</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists</code></td>
<td>Créer playlist</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists/import</code></td>
<td>Importer playlist</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/search</code></td>
<td>Recherche</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/recommendations</code></td>
<td>Recommandations</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/favoris</code></td>
<td>Playlist favoris</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id</code></td>
<td>Détail playlist</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id/analytics</code></td>
<td>Stats playlist</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/playlists/:id</code></td>
<td>Modifier (ownership)</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/playlists/:id</code></td>
<td>Supprimer (ownership)</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists/:id/tracks</code></td>
<td>Ajouter piste</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/playlists/:id/tracks/:track_id</code></td>
<td>Retirer piste</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/playlists/:id/tracks/reorder</code></td>
<td>Réordonner</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists/:id/collaborators</code></td>
<td>Ajouter collaborateur</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id/collaborators</code></td>
<td>Lister collaborateurs</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/playlists/:id/collaborators/:userId</code></td>
<td>Modifier permission</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/playlists/:id/collaborators/:userId</code></td>
<td>Retirer collaborateur</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists/:id/share</code></td>
<td>Créer lien de partage</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id/export/json</code></td>
<td>Export JSON</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id/export/csv</code></td>
<td>Export CSV</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/playlists/:id/export/m3u</code></td>
<td>Export M3U</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/playlists/:id/duplicate</code></td>
<td>Dupliquer</td>
</tr>
</tbody>
</table>
<hr />
<h2>6. Chat &amp; Conversations (<code>/chat</code>, <code>/conversations</code>)</h2>
<p><strong>Fichier</strong> : <code>router.go</code>, <code>routes_core.go</code></p>
<h3>Chat</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/chat/ws</code></td>
<td>WebSocket chat</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/chat/token</code></td>
<td>Obtenir token chat</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/chat/stats</code></td>
<td>Stats chat</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/chat/rooms/:roomId/messages/:messageId/reactions</code></td>
<td>Ajouter réaction</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/chat/rooms/:roomId/messages/:messageId/reactions</code></td>
<td>Retirer réaction</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/chat/rooms/:roomId/messages/search</code></td>
<td>Recherche messages</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/chat/rooms/:roomId/attachments</code></td>
<td>Upload pièce jointe</td>
</tr>
</tbody>
</table>
<h3>Conversations</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/conversations</code></td>
<td>Mes conversations</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/conversations</code></td>
<td>Créer conversation</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/conversations/join/:token</code></td>
<td>Rejoindre par token</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/conversations/:id</code></td>
<td>Détail conversation</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/conversations/:id</code></td>
<td>Modifier conversation</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/conversations/:id</code></td>
<td>Supprimer conversation</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/conversations/:id/leave</code></td>
<td>Quitter</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/conversations/:id/members</code></td>
<td>Lister membres</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/conversations/:id/members</code></td>
<td>Ajouter membre</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/conversations/:id/members/:userId</code></td>
<td>Exclure membre</td>
</tr>
<tr>
<td>🔒 PATCH</td>
<td><code>/conversations/:id/members/:userId</code></td>
<td>Modifier rôle membre</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/conversations/:id/participants</code></td>
<td>Ajouter participant</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/conversations/:id/participants/:userId</code></td>
<td>Retirer participant</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/conversations/:id/invitations</code></td>
<td>Créer invitation</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/conversations/:id/history</code></td>
<td>Historique</td>
</tr>
</tbody>
</table>
<hr />
<h2>7. Social (<code>/social</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_social.go</code></p>
<h3>Endpoints publics</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/social/feed</code></td>
<td>Feed (auth optionnelle)</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/social/explore</code></td>
<td>Explorer</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/social/trending</code></td>
<td>Tendances</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/social/posts/user/:user_id</code></td>
<td>Posts utilisateur</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/social/groups</code></td>
<td>Lister groupes</td>
</tr>
</tbody>
</table>
<h3>Endpoints protégés</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/social/groups/mine</code></td>
<td>Mes groupes</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/social/groups/:id</code></td>
<td>Détail groupe</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/posts</code></td>
<td>Créer post</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/like</code></td>
<td>Toggle like</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/comments</code></td>
<td>Ajouter commentaire</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups</code></td>
<td>Créer groupe</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups/:id/join</code></td>
<td>Rejoindre groupe</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/social/groups/:id/leave</code></td>
<td>Quitter groupe</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups/:id/request</code></td>
<td>Demande adhésion</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/social/groups/:id/requests</code></td>
<td>Demandes en attente</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups/:id/requests/:request_id/approve</code></td>
<td>Approuver</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups/:id/requests/:request_id/reject</code></td>
<td>Rejeter</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/social/groups/:id/invite</code></td>
<td>Inviter membre</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/social/groups/:id/members/:user_id/role</code></td>
<td>Modifier rôle</td>
</tr>
</tbody>
</table>
<hr />
<h2>8. Découverte (<code>/discover</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_discover.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/discover/genres</code></td>
<td>Lister genres</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/discover/genre/:genre</code></td>
<td>Pistes par genre</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/discover/tag/:tag</code></td>
<td>Pistes par tag</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/discover/playlists/editorial</code></td>
<td>Playlists éditoriales</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/discover/genre/:genre/follow</code></td>
<td>Suivre genre</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/discover/genre/:genre/follow</code></td>
<td>Ne plus suivre genre</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/discover/tag/:tag/follow</code></td>
<td>Suivre tag</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/discover/tag/:tag/follow</code></td>
<td>Ne plus suivre tag</td>
</tr>
</tbody>
</table>
<hr />
<h2>9. Recherche (<code>/search</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_search.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/search</code></td>
<td>Recherche unifiée (pistes, users, playlists)</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/search/suggestions</code></td>
<td>Suggestions</td>
</tr>
</tbody>
</table>
<p>Backend : Elasticsearch (fallback PostgreSQL full-text).</p>
<hr />
<h2>10. Feed (<code>/feed</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_feed.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/feed</code></td>
<td>Feed chronologique des pistes</td>
</tr>
</tbody>
</table>
<hr />
<h2>11. File d'attente (<code>/queue</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_queue.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/queue</code></td>
<td>Ma file d'attente</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/queue</code></td>
<td>Mettre à jour</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/queue/items</code></td>
<td>Ajouter item</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/queue/items/:id</code></td>
<td>Retirer item</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/queue</code></td>
<td>Vider la file</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/queue/session</code></td>
<td>Créer session collaborative</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/queue/session/:token</code></td>
<td>Supprimer session</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/queue/session/:token/items</code></td>
<td>Ajouter à session</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/queue/session/:token/items/:id</code></td>
<td>Retirer de session</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/queue/session/:token</code></td>
<td>Voir session (auth optionnelle)</td>
</tr>
</tbody>
</table>
<hr />
<h2>12. Marketplace (<code>/marketplace</code>, <code>/sell</code>, <code>/commerce</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_marketplace.go</code></p>
<h3>Marketplace (publics)</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/marketplace/products</code></td>
<td>Lister produits</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/marketplace/products/:id</code></td>
<td>Détail produit</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/marketplace/products/:id/preview</code></td>
<td>Prévisualiser</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/marketplace/products/:id/reviews</code></td>
<td>Avis</td>
</tr>
</tbody>
</table>
<h3>Marketplace (protégés)</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒🎨 POST</td>
<td><code>/marketplace/products</code></td>
<td>Créer produit</td>
</tr>
<tr>
<td>🔒🎨 POST</td>
<td><code>/marketplace/products/:id/preview</code></td>
<td>Upload preview</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/marketplace/products/:id</code></td>
<td>Modifier (ownership)</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/marketplace/products/:id/images</code></td>
<td>Modifier images</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/orders</code></td>
<td>Mes commandes</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/orders/:id</code></td>
<td>Détail commande</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/orders/:id/invoice</code></td>
<td>Facture</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/marketplace/orders/:id/refund</code></td>
<td>Remboursement</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/marketplace/orders</code></td>
<td>Créer commande</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/download/:product_id</code></td>
<td>URL téléchargement</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/licenses/mine</code></td>
<td>Mes licences</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/marketplace/products/:id/reviews</code></td>
<td>Laisser avis</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/marketplace/wishlist</code></td>
<td>Wishlist</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/marketplace/wishlist</code></td>
<td>Ajouter à wishlist</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/marketplace/wishlist/:productId</code></td>
<td>Retirer de wishlist</td>
</tr>
</tbody>
</table>
<h3>Vendeur (<code>/sell</code>)</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/stats</code></td>
<td>Stats ventes</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/stats/evolution</code></td>
<td>Évolution stats</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/stats/top-products</code></td>
<td>Top produits</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/sales</code></td>
<td>Historique ventes</td>
</tr>
<tr>
<td>🔒🎨 POST</td>
<td><code>/sell/connect/onboard</code></td>
<td>Onboarding Stripe Connect</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/connect/callback</code></td>
<td>Callback Stripe</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/balance</code></td>
<td>Solde</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/transfers</code></td>
<td>Transferts</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/marketplace-balance</code></td>
<td>Solde marketplace</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/payouts</code></td>
<td>Historique versements</td>
</tr>
<tr>
<td>🔒🎨 POST</td>
<td><code>/sell/payouts/request</code></td>
<td>Demander versement</td>
</tr>
<tr>
<td>🔒🎨 POST</td>
<td><code>/sell/kyc/start</code></td>
<td>Démarrer KYC</td>
</tr>
<tr>
<td>🔒🎨 GET</td>
<td><code>/sell/kyc/status</code></td>
<td>Statut KYC</td>
</tr>
</tbody>
</table>
<h3>Commerce (<code>/commerce</code>)</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/commerce/cart</code></td>
<td>Panier</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/commerce/promo/:code</code></td>
<td>Valider code promo</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/commerce/cart/items</code></td>
<td>Ajouter au panier</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/commerce/cart/items/:id</code></td>
<td>Retirer du panier</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/commerce/cart/checkout</code></td>
<td>Payer</td>
</tr>
</tbody>
</table>
<h3>Support</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 POST</td>
<td><code>/support/tickets</code></td>
<td>Soumettre ticket</td>
</tr>
</tbody>
</table>
<hr />
<h2>13. Webhooks (<code>/webhooks</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_webhooks.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 POST</td>
<td><code>/webhooks/hyperswitch</code></td>
<td>Webhook paiement (vérif. signature)</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/webhooks</code></td>
<td>Enregistrer webhook</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/webhooks</code></td>
<td>Lister webhooks</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/webhooks/:id</code></td>
<td>Supprimer webhook</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/webhooks/stats</code></td>
<td>Stats webhooks</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/webhooks/:id/test</code></td>
<td>Tester webhook</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/webhooks/:id/regenerate-key</code></td>
<td>Régénérer clé API</td>
</tr>
</tbody>
</table>
<hr />
<h2>14. Analytics (<code>/analytics</code>, <code>/creator/analytics</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_analytics.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/creator/stats</code></td>
<td>Stats créateur</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/creator/charts</code></td>
<td>Graphiques</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/creator/export</code></td>
<td>Export</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics</code></td>
<td>Analytics globales</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/analytics/events</code></td>
<td>Enregistrer événement</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/tracks/:id</code></td>
<td>Analytics piste</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/traffic-sources</code></td>
<td>Sources de trafic</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/analytics/device-breakdown</code></td>
<td>Répartition appareils</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/dashboard</code></td>
<td>Dashboard créateur</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/plays</code></td>
<td>Évolution lectures</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/sales</code></td>
<td>Ventes</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/discovery</code></td>
<td>Sources découverte</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/geographic</code></td>
<td>Géographie</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/audience</code></td>
<td>Audience</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/live/:streamId</code></td>
<td>Métriques live</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/tracks</code></td>
<td>Pistes</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/export</code></td>
<td>Export analytics</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/heatmap/:trackId</code></td>
<td>Heatmap piste</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/compare</code></td>
<td>Comparaison périodes</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/marketplace</code></td>
<td>Analytics marketplace</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creator/analytics/alerts</code></td>
<td>Alertes métriques</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/creator/analytics/alerts</code></td>
<td>Créer alerte</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/creator/analytics/alerts/preferences</code></td>
<td>Préférences alertes</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/creator/analytics/alerts/:alertId</code></td>
<td>Supprimer alerte</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/creator/analytics/alerts/check</code></td>
<td>Vérifier alertes</td>
</tr>
</tbody>
</table>
<hr />
<h2>15. Modération (<code>/admin/moderation</code>, <code>/reports</code>, <code>/strikes</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_moderation.go</code></p>
<h3>Admin</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/queue</code></td>
<td>File de modération</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/moderation/reports/:id/process</code></td>
<td>Traiter signalement</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/moderation/reports/:id/assign</code></td>
<td>Assigner signalement</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/spam</code></td>
<td>Détections spam</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/fingerprints</code></td>
<td>Empreintes en attente</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/moderation/fingerprints/:trackId/review</code></td>
<td>Vérifier empreinte</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/users/:userId/strikes</code></td>
<td>Strikes utilisateur</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/appeals</code></td>
<td>Appels en attente</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/moderation/appeals/:strikeId/resolve</code></td>
<td>Résoudre appel</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/moderation/stats</code></td>
<td>Stats modération</td>
</tr>
</tbody>
</table>
<h3>Utilisateur</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/reports</code></td>
<td>Signaler contenu</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/me/strikes</code></td>
<td>Mes strikes</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/strikes/:strikeId/appeal</code></td>
<td>Faire appel</td>
</tr>
</tbody>
</table>
<hr />
<h2>16. Administration plateforme (<code>/admin/platform</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_admin_platform.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/platform/metrics</code></td>
<td>Métriques plateforme</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/platform/users</code></td>
<td>Recherche utilisateurs</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/platform/users/:userId</code></td>
<td>Détail utilisateur</td>
</tr>
<tr>
<td>🔒👑 PUT</td>
<td><code>/admin/platform/users/:userId/role</code></td>
<td>Modifier rôle</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/platform/users/:userId/suspend</code></td>
<td>Suspendre</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/platform/users/:userId/unsuspend</code></td>
<td>Réactiver</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/platform/content</code></td>
<td>Recherche contenu</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/platform/content/:id/hide</code></td>
<td>Masquer contenu</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/platform/content/:id/restore</code></td>
<td>Restaurer contenu</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/platform/payments</code></td>
<td>Aperçu paiements</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/platform/orders/:id/refund</code></td>
<td>Rembourser commande</td>
</tr>
</tbody>
</table>
<hr />
<h2>17. Live streaming (<code>/live</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_live.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/live/streams</code></td>
<td>Lister streams live</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/live/streams/:id</code></td>
<td>Détail stream</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/live/streams/me</code></td>
<td>Mes streams</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/live/streams/me/key</code></td>
<td>Clé de stream</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/live/streams/me/key/regenerate</code></td>
<td>Régénérer clé</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/live/streams</code></td>
<td>Créer live</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/live/streams/:id</code></td>
<td>Modifier live</td>
</tr>
<tr>
<td>POST</td>
<td><code>/live/callback/publish</code></td>
<td>Callback RTMP (secret)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/live/callback/publish_done</code></td>
<td>Callback fin RTMP</td>
</tr>
</tbody>
</table>
<hr />
<h2>18. Co-écoute (<code>/co-listening</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_co_listening.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/co-listening/sessions</code></td>
<td>Créer session</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/co-listening/sessions/:id</code></td>
<td>Détail session</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/co-listening/sessions/:id</code></td>
<td>Terminer session</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/co-listening/ws</code></td>
<td>WebSocket co-écoute (JWT en query)</td>
</tr>
</tbody>
</table>
<hr />
<h2>19. Inventaire / Équipement (<code>/inventory</code>, <code>/users/:id/gear</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_gear.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/users/:id/gear</code></td>
<td>Profil équipement public</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/inventory/gear</code></td>
<td>Lister mon équipement</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/inventory/gear</code></td>
<td>Ajouter équipement</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/inventory/gear/:id</code></td>
<td>Détail équipement</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/inventory/gear/:id</code></td>
<td>Modifier</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/inventory/gear/:id</code></td>
<td>Supprimer</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/inventory/gear/:id/images</code></td>
<td>Upload image</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/inventory/gear/:id/images/:img_id</code></td>
<td>Supprimer image</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/inventory/gear/:id/documents</code></td>
<td>Upload document</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/inventory/gear/:id/documents</code></td>
<td>Lister documents</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/inventory/gear/:id/documents/:docId</code></td>
<td>Supprimer document</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/inventory/gear/:id/repairs</code></td>
<td>Ajouter réparation</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/inventory/gear/:id/repairs</code></td>
<td>Lister réparations</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/inventory/gear/:id/repairs/:repairId</code></td>
<td>Supprimer réparation</td>
</tr>
</tbody>
</table>
<hr />
<h2>20. Cloud personnel (<code>/cloud</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_cloud.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/cloud/shared/:token</code></td>
<td>Fichier partagé</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/folders</code></td>
<td>Lister dossiers</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/folders</code></td>
<td>Créer dossier</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/cloud/folders/:id</code></td>
<td>Renommer dossier</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/cloud/folders/:id</code></td>
<td>Supprimer dossier</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/files</code></td>
<td>Lister fichiers</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/files</code></td>
<td>Upload fichier</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/files/:id</code></td>
<td>Détail fichier</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/cloud/files/:id</code></td>
<td>Supprimer fichier</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/files/:id/stream</code></td>
<td>Streamer fichier</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/files/:id/publish</code></td>
<td>Publier comme piste</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/files/:id/versions</code></td>
<td>Lister versions</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/files/:id/versions</code></td>
<td>Créer version</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/files/:id/restore/:version</code></td>
<td>Restaurer version</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/cloud/files/:id/share</code></td>
<td>Partager fichier</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/cloud/quota</code></td>
<td>Quota stockage</td>
</tr>
</tbody>
</table>
<hr />
<h2>21. Abonnements (<code>/subscriptions</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_subscription.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/subscriptions/plans</code></td>
<td>Lister forfaits</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/subscriptions/plans/:id</code></td>
<td>Détail forfait</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/subscriptions/me</code></td>
<td>Mon abonnement</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/subscriptions/subscribe</code></td>
<td>S'abonner</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/subscriptions/cancel</code></td>
<td>Annuler</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/subscriptions/reactivate</code></td>
<td>Réactiver</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/subscriptions/billing-cycle</code></td>
<td>Changer cycle</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/subscriptions/invoices</code></td>
<td>Factures</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/subscriptions/history</code></td>
<td>Historique</td>
</tr>
</tbody>
</table>
<hr />
<h2>22. Distribution (<code>/distributions</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_distribution.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/distributions/submit</code></td>
<td>Soumettre distribution</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/distributions</code></td>
<td>Lister distributions</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/distributions/:id</code></td>
<td>Détail</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/distributions/:id/status-history</code></td>
<td>Historique statut</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/distributions/:id/remove</code></td>
<td>Retirer</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/tracks/:id/distributions</code></td>
<td>Distributions d'une piste</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creators/me/external-royalties</code></td>
<td>Royalties externes</td>
</tr>
</tbody>
</table>
<hr />
<h2>23. Éducation (<code>/courses</code>, <code>/enrollments</code>, <code>/lessons</code>, <code>/certificates</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_education.go</code></p>
<h3>Endpoints publics</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/courses</code></td>
<td>Cours publiés</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/courses/:id</code></td>
<td>Détail cours</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/courses/:id/lessons</code></td>
<td>Leçons du cours</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/courses/:id/reviews</code></td>
<td>Avis</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/courses/slug/:slug</code></td>
<td>Cours par slug</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/certificates/:code</code></td>
<td>Vérifier certificat</td>
</tr>
</tbody>
</table>
<h3>Endpoints protégés</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/courses</code></td>
<td>Créer cours</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/courses/:id</code></td>
<td>Modifier cours</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/courses/:id</code></td>
<td>Supprimer cours</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/publish</code></td>
<td>Publier</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/archive</code></td>
<td>Archiver</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/lessons</code></td>
<td>Créer leçon</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/courses/:id/lessons/:lesson_id</code></td>
<td>Modifier leçon</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/courses/:id/lessons/:lesson_id</code></td>
<td>Supprimer leçon</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/lessons/reorder</code></td>
<td>Réordonner leçons</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/lessons/:lesson_id/video</code></td>
<td>Upload vidéo</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/enroll</code></td>
<td>S'inscrire</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/courses/:id/progress</code></td>
<td>Progression</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/certificate</code></td>
<td>Obtenir certificat</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/courses/:id/reviews</code></td>
<td>Laisser avis</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/enrollments</code></td>
<td>Mes inscriptions</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/lessons/:lesson_id/progress</code></td>
<td>Mise à jour progression</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/certificates</code></td>
<td>Mes certificats</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/creators/me/courses</code></td>
<td>Mes cours (créateur)</td>
</tr>
</tbody>
</table>
<hr />
<h2>24. Tags &amp; Suggestions (<code>/tags</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_tag.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/tags/suggest</code></td>
<td>Suggestions de tags</td>
</tr>
</tbody>
</table>
<hr />
<h2>25. Développeur (<code>/developer</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_developer.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/developer/api-keys</code></td>
<td>Lister clés API</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/developer/api-keys</code></td>
<td>Créer clé API</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/developer/api-keys/:id</code></td>
<td>Supprimer clé API</td>
</tr>
</tbody>
</table>
<hr />
<h2>26. Sessions (<code>/sessions</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 POST</td>
<td><code>/sessions/logout</code></td>
<td>Déconnexion</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/sessions/logout-all</code></td>
<td>Déconnexion tous appareils</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/sessions/logout-others</code></td>
<td>Déconnexion autres appareils</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/sessions</code></td>
<td>Sessions actives</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/sessions/:session_id</code></td>
<td>Révoquer session</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/sessions/stats</code></td>
<td>Stats sessions</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/sessions/refresh</code></td>
<td>Rafraîchir session</td>
</tr>
</tbody>
</table>
<hr />
<h2>27. Uploads (<code>/uploads</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/upload/limits</code></td>
<td>Limites upload</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/upload/validate-type</code></td>
<td>Valider type fichier</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/uploads/</code></td>
<td>Upload fichier</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/uploads/batch</code></td>
<td>Upload batch</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/uploads/:id/status</code></td>
<td>Statut upload</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/uploads/:id/progress</code></td>
<td>Progression</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/uploads/:id</code></td>
<td>Supprimer upload</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/uploads/stats</code></td>
<td>Stats uploads</td>
</tr>
</tbody>
</table>
<hr />
<h2>28. Notifications (<code>/notifications</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/notifications</code></td>
<td>Mes notifications</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/notifications/unread-count</code></td>
<td>Non lues</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/notifications/preferences</code></td>
<td>Préférences</td>
</tr>
<tr>
<td>🔒 PUT</td>
<td><code>/notifications/preferences</code></td>
<td>Modifier préférences</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/notifications/push/subscribe</code></td>
<td>S'abonner push</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/notifications/:id/read</code></td>
<td>Marquer lue</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/notifications/read-all</code></td>
<td>Tout marquer lu</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/notifications/:id</code></td>
<td>Supprimer</td>
</tr>
<tr>
<td>🔒 DELETE</td>
<td><code>/notifications</code></td>
<td>Tout supprimer</td>
</tr>
</tbody>
</table>
<hr />
<h2>29. Audit (<code>/audit</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒 GET</td>
<td><code>/audit/logs</code></td>
<td>Rechercher logs</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/audit/stats</code></td>
<td>Stats audit</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/audit/activity</code></td>
<td>Activité utilisateur</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/audit/suspicious</code></td>
<td>Activité suspecte</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/audit/ip/:ip</code></td>
<td>Activité d'une IP</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/audit/logs/:id</code></td>
<td>Détail log</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/audit/cleanup</code></td>
<td>Nettoyage anciens logs</td>
</tr>
</tbody>
</table>
<hr />
<h2>30. Santé &amp; Métriques (publics)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/health</code></td>
<td>Santé basique</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/health/deep</code></td>
<td>Santé approfondie</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/healthz</code></td>
<td>Liveness probe</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/readyz</code></td>
<td>Readiness probe</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/api/v1/status</code></td>
<td>Statut API</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/api/v1/csrf-token</code></td>
<td>Token CSRF</td>
</tr>
<tr>
<td>🔓 POST</td>
<td><code>/api/v1/logs/frontend</code></td>
<td>Logs frontend ⚡</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/api/v1/announcements/active</code></td>
<td>Annonces actives</td>
</tr>
<tr>
<td>GET</td>
<td><code>/metrics</code></td>
<td>Prometheus (protégé metrics middleware)</td>
</tr>
</tbody>
</table>
<hr />
<h2>31. Administration générale (<code>/admin</code>)</h2>
<p><strong>Fichier</strong> : <code>routes_core.go</code></p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/audit/logs</code></td>
<td>Logs audit admin</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/audit/stats</code></td>
<td>Stats audit admin</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/audit/suspicious</code></td>
<td>Activité suspecte</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/reports</code></td>
<td>Signalements</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/reports/:id/resolve</code></td>
<td>Résoudre signalement</td>
</tr>
<tr>
<td>🔒👑 PUT</td>
<td><code>/admin/maintenance</code></td>
<td>Mode maintenance</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/maintenance</code></td>
<td>Statut maintenance</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/announcements</code></td>
<td>Lister annonces</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/announcements</code></td>
<td>Créer annonce</td>
</tr>
<tr>
<td>🔒👑 DELETE</td>
<td><code>/admin/announcements/:id</code></td>
<td>Supprimer annonce</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/transfers</code></td>
<td>Transferts</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/transfers/:id/retry</code></td>
<td>Relancer transfert</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/auth/unlock-account</code></td>
<td>Débloquer compte</td>
</tr>
<tr>
<td>🔒👑 POST</td>
<td><code>/admin/search/reindex</code></td>
<td>Réindexer Elasticsearch</td>
</tr>
<tr>
<td>🔒👑 GET</td>
<td><code>/admin/feature-flags</code></td>
<td>Feature flags</td>
</tr>
<tr>
<td>🔒👑 PUT</td>
<td><code>/admin/feature-flags/:name</code></td>
<td>Toggle feature flag</td>
</tr>
</tbody>
</table>
<hr />
<h2>32. Routes internes (entre services)</h2>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/internal/tracks/:id/stream-ready</code></td>
<td>Callback stream (header X-Stream-Secret)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/internal/stream-events</code></td>
<td>Événements stream</td>
</tr>
</tbody>
</table>
<hr />
<h2>Serveur Streaming Rust (Axum)</h2>
<p>Voir [[SERVEUR_STREAMING_RUST]] pour le détail complet.</p>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>🔓 GET</td>
<td><code>/</code></td>
<td>Info serveur</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/health</code></td>
<td>Santé</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/healthz</code></td>
<td>Liveness</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/readyz</code></td>
<td>Readiness</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/metrics</code></td>
<td>Prometheus</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/stream/{filename}</code></td>
<td>Stream audio (signature)</td>
</tr>
<tr>
<td>POST</td>
<td><code>/internal/jobs/transcode</code></td>
<td>Job transcoding (clé interne)</td>
</tr>
<tr>
<td>🔒 POST</td>
<td><code>/v1/stream/transcode</code></td>
<td>Initier transcoding</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/v1/stream/job/{id}</code></td>
<td>Statut job</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/v1/stream/hls/{job_id}/index.m3u8</code></td>
<td>Playlist HLS</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/v1/stream/hls/{job_id}/{segment}</code></td>
<td>Segment HLS</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/hls/{track_id}/master.m3u8</code></td>
<td>Master playlist</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/hls/{track_id}/{quality}/playlist.m3u8</code></td>
<td>Playlist qualité</td>
</tr>
<tr>
<td>🔒 GET</td>
<td><code>/hls/{track_id}/{quality}/{segment}</code></td>
<td>Segment qualité</td>
</tr>
<tr>
<td>🔓 GET</td>
<td><code>/ws</code></td>
<td>WebSocket streaming</td>
</tr>
</tbody>
</table>
<hr />
<h2>Pile de middlewares globaux (ordre d'exécution)</h2>
<ol>
<li><strong>CORS</strong> — Validation des origines</li>
<li><strong>Cache Headers</strong> — En-têtes CDN</li>
<li><strong>Maintenance Mode</strong> — Retourne 503 sauf /health et /admin</li>
<li><strong>Request Logger</strong> — Logs structurés</li>
<li><strong>Prometheus Metrics</strong> — Compteurs/histogrammes</li>
<li><strong>Sentry Recovery</strong> — Capture d'erreurs</li>
<li><strong>Security Headers</strong> — HSTS, CSP, X-Content-Type-Options</li>
<li><strong>CCPA Header</strong> — Sec-GPC</li>
<li><strong>Audit Middleware</strong> — Auto-log POST/PUT/DELETE</li>
<li><strong>API Monitoring</strong> — Suivi pannes/alertes</li>
<li><strong>Error Handler</strong> — Stack traces en dev/DEBUG</li>
<li><strong>Recovery</strong> — Récupération après panic</li>
<li><strong>Request ID</strong> — UUID par requête</li>
<li><strong>Global Timeout</strong> — Timeout global</li>
<li><strong>DDoS Rate Limiting</strong> — 1000 req/s global, 100/s par IP</li>
<li><strong>Rate Limiter</strong> — Général</li>
<li><strong>Response Cache</strong> — Redis, TTL 5-15 min</li>
</ol>
<hr />
<h2>Documents liés</h2>
<ul>
<li>[[ARCHITECTURE_VEZA]] — Architecture globale</li>
<li>[[SCHEMA_BASE_DE_DONNEES]] — Schéma PostgreSQL</li>
<li>[[SERVEUR_STREAMING_RUST]] — Serveur Rust détaillé</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 SERVEUR_STREAMING_RUST.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/APIs_&_Rust_Modules/SERVEUR_STREAMING_RUST.md</div>
</div>
<h1>Serveur Streaming Rust (veza-stream-server)</h1>
<blockquote>
<p>Architecture du serveur audio haute performance basé sur Axum.
Source : <code>veza-stream-server/src/</code></p>
</blockquote>
<h2>Vue d'ensemble</h2>
<p>Le stream server est un service Rust autonome qui gère :
- <strong>Streaming audio HLS adaptatif</strong> multi-bitrate
- <strong>WebSocket temps réel</strong> pour la lecture synchronisée
- <strong>Transcoding audio</strong> via FFmpeg (WAV → MP3, HLS)
- <strong>Métriques Prometheus</strong> pour le monitoring</p>
<p>Il communique avec le backend Go via RabbitMQ (événements asynchrones) et des callbacks REST internes.</p>
<h2>Stack technique</h2>
<table>
<thead>
<tr>
<th>Composant</th>
<th>Technologie</th>
</tr>
</thead>
<tbody>
<tr>
<td>Framework HTTP</td>
<td>Axum (async Rust)</td>
</tr>
<tr>
<td>Runtime</td>
<td>Tokio</td>
</tr>
<tr>
<td>Auth</td>
<td>JWT (validation)</td>
</tr>
<tr>
<td>Audio</td>
<td>FFmpeg (transcoding), HLS.js (côté client)</td>
</tr>
<tr>
<td>Messaging</td>
<td>RabbitMQ (consommateur)</td>
</tr>
<tr>
<td>Cache</td>
<td>In-memory (playlists HLS)</td>
</tr>
<tr>
<td>Monitoring</td>
<td>Prometheus</td>
</tr>
<tr>
<td>Stockage</td>
<td>S3/MinIO</td>
</tr>
<tr>
<td>Base de données</td>
<td>PostgreSQL (requêtes directes)</td>
</tr>
</tbody>
</table>
<h2>Structure des modules</h2>
<pre><code>veza-stream-server/src/
├── main.rs # Point d'entrée, initialisation Axum
├── routes/
│ └── api.rs # Définition de toutes les routes
├── streaming/
│ ├── websocket.rs # Handler WebSocket avec protocole de sync
│ ├── hls.rs # Génération de playlists HLS
│ └── adaptive.rs # Sélection adaptative du bitrate
├── transcoding/
│ └── mod.rs # Orchestration transcoding FFmpeg
├── codecs/
│ └── mod.rs # Wrappers FFmpeg
├── audio/
│ └── mod.rs # Utilitaires audio
├── middleware/
│ ├── logging.rs # Logs structurés
│ ├── security.rs # Headers de sécurité
│ └── rate_limit.rs # Rate limiting par IP
├── auth/
│ └── mod.rs # Validation JWT
├── config/
│ └── mod.rs # Configuration depuis env
├── health/
│ └── mod.rs # Health checks
├── monitoring/
│ └── mod.rs # Métriques Prometheus
├── cache/
│ └── mod.rs # Cache mémoire playlists
└── database/
└── mod.rs # Requêtes PostgreSQL
</code></pre>
<h2>Routes</h2>
<h3>Santé &amp; monitoring</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/</code></td>
<td>Message racine (info serveur)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/health</code></td>
<td>Health check détaillé</td>
</tr>
<tr>
<td>GET</td>
<td><code>/healthz</code></td>
<td>Liveness probe (Kubernetes)</td>
</tr>
<tr>
<td>GET</td>
<td><code>/readyz</code></td>
<td>Readiness probe</td>
</tr>
<tr>
<td>GET</td>
<td><code>/metrics</code></td>
<td>Métriques Prometheus</td>
</tr>
</tbody>
</table>
<h3>Streaming audio</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
<th>Auth</th>
</tr>
</thead>
<tbody>
<tr>
<td>GET</td>
<td><code>/stream/{filename}</code></td>
<td>Stream fichier audio</td>
<td>Signature URL</td>
</tr>
<tr>
<td>GET</td>
<td><code>/hls/{track_id}/master.m3u8</code></td>
<td>Playlist master HLS</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/hls/{track_id}/{quality}/playlist.m3u8</code></td>
<td>Playlist par qualité</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/hls/{track_id}/{quality}/{segment}</code></td>
<td>Segment HLS</td>
<td>JWT</td>
</tr>
<tr>
<td>WS</td>
<td><code>/ws</code></td>
<td>WebSocket streaming temps réel</td>
<td>JWT (query param)</td>
</tr>
</tbody>
</table>
<h3>Transcoding</h3>
<table>
<thead>
<tr>
<th>Méthode</th>
<th>Route</th>
<th>Description</th>
<th>Auth</th>
</tr>
</thead>
<tbody>
<tr>
<td>POST</td>
<td><code>/internal/jobs/transcode</code></td>
<td>Lancer job transcoding</td>
<td>Clé API interne</td>
</tr>
<tr>
<td>POST</td>
<td><code>/v1/stream/transcode</code></td>
<td>Initier transcoding</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/v1/stream/job/{id}</code></td>
<td>Statut du job</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/v1/stream/hls/{job_id}/index.m3u8</code></td>
<td>Playlist du job</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/v1/stream/hls/{job_id}/{segment}</code></td>
<td>Segment du job</td>
<td>JWT</td>
</tr>
<tr>
<td>GET</td>
<td><code>/api/streams/jobs/{id}/status</code></td>
<td>Statut détaillé</td>
<td>JWT</td>
</tr>
</tbody>
</table>
<h2>Protocole WebSocket</h2>
<p>Le WebSocket (<code>/ws?track_id=...</code>) utilise un protocole personnalisé pour la lecture synchronisée.</p>
<h3>Commandes client → serveur</h3>
<table>
<thead>
<tr>
<th>Commande</th>
<th>Payload</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>play</code></td>
<td><code>{ position: float }</code></td>
<td>Démarrer la lecture à la position</td>
</tr>
<tr>
<td><code>pause</code></td>
<td></td>
<td>Mettre en pause</td>
</tr>
<tr>
<td><code>seek</code></td>
<td><code>{ position: float }</code></td>
<td>Sauter à une position</td>
</tr>
<tr>
<td><code>sync</code></td>
<td><code>{ position: float }</code></td>
<td>Synchroniser (co-écoute)</td>
</tr>
<tr>
<td><code>heartbeat</code></td>
<td></td>
<td>Maintenir la connexion</td>
</tr>
<tr>
<td><code>error</code></td>
<td><code>{ message: string }</code></td>
<td>Signaler une erreur client</td>
</tr>
</tbody>
</table>
<h3>Réponses serveur → client</h3>
<table>
<thead>
<tr>
<th>Réponse</th>
<th>Payload</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ready</code></td>
<td><code>{ duration, bitrates }</code></td>
<td>Serveur prêt à streamer</td>
</tr>
<tr>
<td><code>chunk</code></td>
<td><code>{ data, position, bitrate }</code></td>
<td>Chunk audio avec métadonnées</td>
</tr>
<tr>
<td><code>sync_ack</code></td>
<td><code>{ position }</code></td>
<td>Accusé de réception sync</td>
</tr>
<tr>
<td><code>error</code></td>
<td><code>{ code, message }</code></td>
<td>Erreur serveur</td>
</tr>
</tbody>
</table>
<h3>Cycle de vie d'une connexion</h3>
<pre><code>Client Serveur
│ │
│── WS Connect ─────────────────▶│
│ (track_id, JWT en query) │
│ │── Validation JWT
│ │── Chargement playlist HLS
│◀── ready ──────────────────────│
│ (duration, bitrates) │
│ │
│── play { position: 0 } ──────▶│
│ │── Sélection bitrate adaptatif
│◀── chunk ──────────────────────│
│◀── chunk ──────────────────────│
│◀── chunk ──────────────────────│
│ │
│── heartbeat ──────────────────▶│ (toutes les 30s)
│ │
│── seek { position: 120 } ────▶│
│◀── chunk ──────────────────────│
│ │
│── pause ──────────────────────▶│
│ │
│── WS Close ───────────────────▶│
</code></pre>
<h2>Streaming HLS adaptatif</h2>
<p>Le serveur génère et sert des playlists HLS multi-bitrate avec sélection adaptative.</p>
<h3>Qualités disponibles</h3>
<p>Le transcoding produit plusieurs qualités à partir du fichier source :</p>
<table>
<thead>
<tr>
<th>Qualité</th>
<th>Bitrate</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>low</td>
<td>64 kbps</td>
<td>Mobile / réseau faible</td>
</tr>
<tr>
<td>medium</td>
<td>128 kbps</td>
<td>Standard</td>
</tr>
<tr>
<td>high</td>
<td>256 kbps</td>
<td>Haute qualité</td>
</tr>
<tr>
<td>lossless</td>
<td>Original</td>
<td>Audiophile</td>
</tr>
</tbody>
</table>
<h3>Fonctionnement</h3>
<ol>
<li><strong>Upload</strong> : Le backend Go reçoit le fichier audio et le stocke sur S3</li>
<li><strong>Événement</strong> : <code>track.uploaded</code> publié sur RabbitMQ</li>
<li><strong>Transcoding</strong> : Le stream server consomme l'événement, transcode via FFmpeg</li>
<li><strong>Stockage</strong> : Les segments HLS et playlists sont stockés sur S3</li>
<li><strong>Callback</strong> : <code>POST /internal/tracks/:id/stream-ready</code> vers le backend</li>
<li><strong>Lecture</strong> : Le client demande le master playlist, le serveur sert les segments</li>
</ol>
<h3>Structure des fichiers HLS sur S3</h3>
<pre><code>hls/{track_id}/
├── master.m3u8 # Master playlist (liste les qualités)
├── low/
│ ├── playlist.m3u8 # Playlist 64 kbps
│ ├── segment_000.ts # Segments audio
│ ├── segment_001.ts
│ └── ...
├── medium/
│ ├── playlist.m3u8 # Playlist 128 kbps
│ └── ...
├── high/
│ ├── playlist.m3u8 # Playlist 256 kbps
│ └── ...
└── lossless/
├── playlist.m3u8
└── ...
</code></pre>
<h2>Pile de middlewares</h2>
<p>Appliqués dans l'ordre à toutes les routes :</p>
<ol>
<li><strong>CORS</strong> — Restrictif par défaut, origines configurables</li>
<li><strong>Timeout</strong> — 30 secondes par requête</li>
<li><strong>Compression</strong> — gzip</li>
<li><strong>Security Headers</strong> — X-Content-Type-Options, X-Frame-Options, etc.</li>
<li><strong>Rate Limiting</strong> — Par IP</li>
<li><strong>Request Logging</strong> — Logs JSON structurés</li>
</ol>
<h2>Configuration</h2>
<pre><code class="language-rust">pub struct Config {
port: u16, // PORT (default: 18082)
database_url: String, // DATABASE_URL
rabbit_mq: RabbitMQConfig, // RABBITMQ_URL
s3_bucket: String, // S3_BUCKET
s3_region: String, // S3_REGION
log_level: String, // LOG_LEVEL
allowed_origins: Vec&lt;String&gt;, // ALLOWED_ORIGINS
}
</code></pre>
<p>Toute la configuration provient des variables d'environnement (voir [[CONFIGURATION_ENVIRONNEMENT]]).</p>
<h2>Communication inter-services</h2>
<h3>Événements RabbitMQ consommés</h3>
<table>
<thead>
<tr>
<th>Événement</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>track.uploaded</code></td>
<td>Lance le transcoding HLS</td>
</tr>
<tr>
<td><code>track.deleted</code></td>
<td>Supprime les segments HLS de S3</td>
</tr>
</tbody>
</table>
<h3>Callbacks REST vers le backend Go</h3>
<table>
<thead>
<tr>
<th>Callback</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>POST /internal/tracks/:id/stream-ready</code></td>
<td>Notifie que le transcoding est terminé</td>
</tr>
<tr>
<td><code>POST /internal/stream-events</code></td>
<td>Envoie des événements de streaming</td>
</tr>
</tbody>
</table>
<p>Header d'authentification : <code>X-Stream-Secret</code></p>
<h2>Dockerfiles</h2>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Base</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Dockerfile</code></td>
<td><code>rust:latest</code></td>
<td>Développement (avec debug symbols)</td>
</tr>
<tr>
<td><code>Dockerfile.production</code></td>
<td><code>debian:bookworm-slim</code></td>
<td>Production (binaire optimisé, minimal)</td>
</tr>
</tbody>
</table>
<h2>Documents liés</h2>
<ul>
<li>[[ARCHITECTURE_VEZA]] — Architecture globale</li>
<li>[[ROUTES_API]] — Référence complète des routes</li>
<li>[[CONFIGURATION_ENVIRONNEMENT]] — Variables d'environnement</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 ARCHITECTURE_VEZA.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/ARCHITECTURE_VEZA.md</div>
</div>
<h1>Architecture Technique Veza</h1>
<blockquote>
<p>Document de référence — architecture globale de la plateforme Veza.
Source : code dans <code>/home/senke/git/talas/veza/</code></p>
</blockquote>
<h2>Vue d'ensemble</h2>
<p>Veza est une plateforme musicale communautaire composée de <strong>3 services principaux</strong> communiquant via REST, WebSocket et RabbitMQ, le tout orchestré par Docker Compose.</p>
<pre><code>┌─────────────────────────────────────────────────────────────┐
│ FRONTEND │
│ React 18 · TypeScript · Tailwind │
│ Vite · Zustand · TanStack Query │
│ (apps/web/) │
└──────────┬──────────────┬───────────────┬────────────────────┘
│ REST /api/v1 │ WS /chat/ws │ WS /stream
▼ ▼ ▼
┌──────────────────┐ ┌────────────────────────────────────────┐
│ BACKEND API │ │ STREAM SERVER │
│ Go · Gin · GORM │ │ Rust · Axum │
│ (veza-backend- │ │ (veza-stream-server/) │
│ api/) │ │ │
│ │ │ • Streaming audio (HLS adaptatif) │
│ • 500+ endpoints│ │ • WebSocket temps réel │
│ • Auth JWT RS256│ │ • Transcoding FFmpeg │
│ • RBAC complet │ │ • Métriques Prometheus │
│ • CSRF Redis │ │ │
│ • Rate limiting │ └──────────────┬─────────────────────────┘
│ • Audit logging │ │
└──────┬───────────┘ │
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE │
│ │
│ PostgreSQL 16 (:15432) Redis 7 (:16379) │
│ Elasticsearch (:19200) RabbitMQ 3 (:15672) │
│ MinIO S3 (:19000) ClamAV 1.4 (:13310) │
└──────────────────────────────────────────────────────────────┘
</code></pre>
<h2>Structure du monorepo</h2>
<pre><code>veza/
├── apps/web/ # Frontend React (SPA + PWA)
├── veza-backend-api/ # API REST Go (Gin + GORM)
├── veza-stream-server/ # Serveur streaming Rust (Axum)
├── veza-common/ # Code partagé Go/Rust
├── veza-docs/ # Documentation technique
├── veza-desktop/ # Wrapper Electron (charge apps/web)
├── docker-compose.yml # Infra développement
├── docker-compose.prod.yml # Infra production
├── docker-compose.dev.yml # Services dev uniquement
├── .env.example # Template de configuration
├── Makefile # Commandes build &amp; dev
└── k8s/ # Manifests Kubernetes
</code></pre>
<h2>Ports réseau (isolation pour éviter les conflits)</h2>
<table>
<thead>
<tr>
<th>Service</th>
<th>Port</th>
<th>Protocole</th>
</tr>
</thead>
<tbody>
<tr>
<td>Backend API</td>
<td>18080</td>
<td>HTTP/REST</td>
</tr>
<tr>
<td>Stream Server</td>
<td>18082</td>
<td>HTTP/WS</td>
</tr>
<tr>
<td>Frontend (dev)</td>
<td>5173</td>
<td>HTTP</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td>15432</td>
<td>TCP</td>
</tr>
<tr>
<td>Redis</td>
<td>16379</td>
<td>TCP</td>
</tr>
<tr>
<td>RabbitMQ AMQP</td>
<td>15672</td>
<td>AMQP</td>
</tr>
<tr>
<td>RabbitMQ Management</td>
<td>25672</td>
<td>HTTP</td>
</tr>
<tr>
<td>ClamAV</td>
<td>13310</td>
<td>TCP</td>
</tr>
<tr>
<td>MinIO S3</td>
<td>19000</td>
<td>HTTP</td>
</tr>
<tr>
<td>Elasticsearch</td>
<td>19200</td>
<td>HTTP</td>
</tr>
</tbody>
</table>
<h2>Stack technique détaillée</h2>
<h3>Backend API (Go)</h3>
<ul>
<li><strong>Framework</strong> : Gin</li>
<li><strong>ORM</strong> : GORM v1 (mapping uniquement, migrations en SQL pur)</li>
<li><strong>Auth</strong> : JWT RS256 (fallback HS256), OAuth 2.0, WebAuthn/Passkeys, MFA TOTP</li>
<li><strong>Sécurité</strong> : CSRF Redis, rate limiting multi-niveaux, ClamAV antivirus, audit logging</li>
<li><strong>Recherche</strong> : Elasticsearch (fallback PostgreSQL full-text)</li>
<li><strong>Stockage</strong> : MinIO S3 compatible</li>
<li><strong>Monitoring</strong> : Prometheus, Sentry</li>
<li><strong>Base</strong> : PostgreSQL 16 avec UUIDs, soft deletes</li>
</ul>
<h3>Stream Server (Rust)</h3>
<ul>
<li><strong>Framework</strong> : Axum (async)</li>
<li><strong>Audio</strong> : HLS adaptatif multi-bitrate, FFmpeg transcoding</li>
<li><strong>Temps réel</strong> : WebSocket avec protocole de synchronisation</li>
<li><strong>Résilience</strong> : Circuit breaker, rate limiting, health checks</li>
<li><strong>Messaging</strong> : RabbitMQ pour les jobs de transcoding asynchrones</li>
</ul>
<h3>Frontend (React)</h3>
<ul>
<li><strong>Build</strong> : Vite v7.1.5</li>
<li><strong>UI</strong> : Tailwind CSS v4, Radix UI</li>
<li><strong>État</strong> : Zustand (stores), TanStack Query (data fetching)</li>
<li><strong>Formulaires</strong> : React Hook Form + Zod (validation)</li>
<li><strong>Routage</strong> : React Router v6 (52+ routes)</li>
<li><strong>i18n</strong> : i18next (EN/FR/ES)</li>
<li><strong>Tests</strong> : Vitest, Storybook</li>
<li><strong>Desktop</strong> : Electron wrapper (même codebase)</li>
</ul>
<h2>Patterns architecturaux</h2>
<h3>1. Microservices avec communication asynchrone</h3>
<ul>
<li>Backend API (Go) → logique métier</li>
<li>Stream Server (Rust) → streaming audio haute performance</li>
<li>RabbitMQ → distribution d'événements entre services</li>
</ul>
<h3>2. Event-driven architecture</h3>
<p>Événements principaux via RabbitMQ :
- <code>user.created</code> → notifications, indexation
- <code>track.uploaded</code> → scan ClamAV → transcoding HLS → indexation Elasticsearch
- <code>payment.completed</code> → mise à jour commande, notification vendeur</p>
<h3>3. Sécurité en profondeur</h3>
<ul>
<li>CSRF tokens (Redis-backed, obligatoire en production)</li>
<li>JWT RS256 avec rotation de clés</li>
<li>OAuth 2.0 avec chiffrement des tokens</li>
<li>ClamAV pour scan antivirus des uploads</li>
<li>Rate limiting : global (1000 req/s), par IP (100 req/s), par endpoint</li>
<li>Verrouillage de compte après N tentatives échouées</li>
<li>Audit logging automatique sur POST/PUT/DELETE</li>
<li>Headers de sécurité (HSTS, CSP, X-Content-Type-Options)</li>
<li>CCPA/RGPD : opt-out, export de données, suppression de compte</li>
</ul>
<h3>4. Observabilité</h3>
<ul>
<li>Logs structurés (JSON en production, texte en dev)</li>
<li>Métriques Prometheus (endpoints <code>/metrics</code>)</li>
<li>Sentry pour le tracking d'erreurs</li>
<li>Health checks (liveness <code>/healthz</code>, readiness <code>/readyz</code>)</li>
</ul>
<h2>Flux de données principaux</h2>
<h3>Inscription utilisateur</h3>
<pre><code>Frontend POST /api/v1/auth/register
→ Backend : validation Zod, hash bcrypt, création User en DB
→ Email de vérification envoyé
→ JWT retourné en cookie HTTP-only
→ Frontend redirige vers /verify-email
</code></pre>
<h3>Upload de piste audio</h3>
<pre><code>Frontend POST /api/v1/tracks (multipart/form-data)
→ Backend : scan ClamAV → stockage S3 → parsing métadonnées FFmpeg
→ Création Track en DB (status: processing)
→ Publication événement track.uploaded sur RabbitMQ
Stream Server (écoute RabbitMQ)
→ Réception track.uploaded
→ Transcoding WAV → MP3 + HLS multi-bitrate
→ Upload segments sur S3
→ Publication transcoding.completed
Backend (écoute événement)
→ Mise à jour Track (status: completed, stream_manifest_url)
→ Indexation Elasticsearch
→ Notification utilisateur
</code></pre>
<h3>Lecture audio streaming</h3>
<pre><code>Frontend WS /stream?track_id=xxx&amp;position=0
→ Stream Server : validation JWT
→ Chargement playlist HLS depuis cache/S3
→ Sélection bitrate adaptatif selon réseau
→ Envoi chunks audio via WebSocket
→ Heartbeat toutes les 30s
</code></pre>
<h2>Commandes de développement</h2>
<pre><code class="language-bash"># Stack complète (backend Docker, web local)
make dev
# Tous les services locaux avec hot reload
make dev-full
# Infrastructure seule (DB, Redis, RabbitMQ, MinIO)
docker-compose up -d
# Services individuels
make dev-web # Frontend seul
make dev-backend-api # Backend seul
make dev-stream-server # Stream server seul
# Build production
make build
# Migrations base de données
cd veza-backend-api &amp;&amp; go run ./cmd/migrate_tool/main.go up
</code></pre>
<h2>Fichiers clés (chemins absolus)</h2>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Rôle</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>veza-backend-api/cmd/api/main.go</code></td>
<td>Point d'entrée backend</td>
</tr>
<tr>
<td><code>veza-backend-api/internal/api/router.go</code></td>
<td>Enregistrement des routes</td>
</tr>
<tr>
<td><code>veza-backend-api/internal/config/config.go</code></td>
<td>Configuration</td>
</tr>
<tr>
<td><code>veza-backend-api/internal/models/</code></td>
<td>68 modèles GORM</td>
</tr>
<tr>
<td><code>veza-backend-api/internal/middleware/</code></td>
<td>17 middlewares</td>
</tr>
<tr>
<td><code>veza-backend-api/migrations/</code></td>
<td>115 fichiers SQL</td>
</tr>
<tr>
<td><code>veza-stream-server/src/main.rs</code></td>
<td>Point d'entrée streaming</td>
</tr>
<tr>
<td><code>veza-stream-server/src/routes/api.rs</code></td>
<td>Routes Axum</td>
</tr>
<tr>
<td><code>veza-stream-server/src/streaming/</code></td>
<td>WebSocket, HLS, adaptatif</td>
</tr>
<tr>
<td><code>apps/web/src/main.tsx</code></td>
<td>Point d'entrée frontend</td>
</tr>
<tr>
<td><code>apps/web/src/router/routeConfig.tsx</code></td>
<td>Définition des routes</td>
</tr>
<tr>
<td><code>apps/web/src/stores/</code></td>
<td>Stores Zustand</td>
</tr>
<tr>
<td><code>apps/web/src/services/api/</code></td>
<td>Clients API</td>
</tr>
<tr>
<td><code>docker-compose.yml</code></td>
<td>Stack développement</td>
</tr>
<tr>
<td><code>docker-compose.prod.yml</code></td>
<td>Stack production</td>
</tr>
<tr>
<td><code>.env.example</code></td>
<td>Template de configuration</td>
</tr>
</tbody>
</table>
<h2>Documents liés</h2>
<ul>
<li>[[ROUTES_API]] — Référence complète des endpoints API</li>
<li>[[SCHEMA_BASE_DE_DONNEES]] — Schéma PostgreSQL complet (60+ tables)</li>
<li>[[SERVEUR_STREAMING_RUST]] — Architecture du serveur Rust Axum</li>
<li>[[FRONTEND_REACT]] — Architecture frontend React</li>
<li>[[CONFIGURATION_ENVIRONNEMENT]] — Variables d'environnement et Docker</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Auth_&_Core/README.md</div>
</div>
<h1>Authentification &amp; Core System</h1>
<p>Ce dossier contient les composants de base partagés entre les applications Talas : gestion des utilisateurs, sécurité, sessions, permissions et configuration générale.</p>
<h2>Objectifs :</h2>
<ul>
<li>Centraliser la logique dauthentification (JWT, OAuth, SSO).</li>
<li>Fournir des services partagés pour toutes les apps (profil, sessions, ACL).</li>
<li>Préparer une architecture modulaire facilement extensible.</li>
</ul>
<h2>Contenu recommandé :</h2>
<ul>
<li><code>auth_controller.go</code> / <code>users.rs</code> (ou équivalents)</li>
<li><code>middleware/</code> : validation de token, CORS, accès protégé</li>
<li><code>schemas/</code> : modèles utilisateur, rôles, permissions</li>
<li><code>utils/</code> : génération tokens, encodage, vérification</li>
<li><code>tests/</code> : login/signup, forgot password, refresh</li>
</ul>
<blockquote>
<p>Ce module doit être le plus découpé possible pour pouvoir sadapter à la croissance future.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 CONFIGURATION_ENVIRONNEMENT.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/CONFIGURATION_ENVIRONNEMENT.md</div>
</div>
<h1>Configuration &amp; Environnement Veza</h1>
<blockquote>
<p>Variables d'environnement, Docker Compose, et paramètres de déploiement.
Source : <code>.env.example</code>, <code>docker-compose*.yml</code>, <code>Makefile</code></p>
</blockquote>
<h2>Variables d'environnement</h2>
<p>Toutes les variables sont définies dans <code>.env.example</code> à la racine du monorepo.</p>
<h3>Core</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>APP_DOMAIN</code></td>
<td><code>veza.fr</code></td>
<td>Domaine principal</td>
</tr>
<tr>
<td><code>FRONTEND_URL</code></td>
<td><code>http://veza.fr:5173</code></td>
<td>URL frontend</td>
</tr>
<tr>
<td><code>PORT_BACKEND</code></td>
<td><code>18080</code></td>
<td>Port API backend</td>
</tr>
<tr>
<td><code>PORT_STREAM</code></td>
<td><code>18082</code></td>
<td>Port stream server</td>
</tr>
<tr>
<td><code>APP_ENV</code></td>
<td><code>development</code></td>
<td>Environnement (development/staging/production)</td>
</tr>
</tbody>
</table>
<h3>Base de données (PostgreSQL)</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>DB_USER</code></td>
<td><code>veza</code></td>
<td>Utilisateur PostgreSQL</td>
</tr>
<tr>
<td><code>DB_PASSWORD</code></td>
<td><code>password</code></td>
<td>Mot de passe</td>
</tr>
<tr>
<td><code>DB_NAME</code></td>
<td><code>veza</code></td>
<td>Nom de la base</td>
</tr>
<tr>
<td><code>DATABASE_URL</code></td>
<td><code>postgres://veza:password@localhost:15432/veza?sslmode=disable</code></td>
<td>URL complète</td>
</tr>
</tbody>
</table>
<h3>Redis</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>REDIS_URL</code></td>
<td><code>redis://:password@localhost:16379</code></td>
<td>URL Redis</td>
</tr>
<tr>
<td><code>REDIS_PASSWORD</code></td>
<td><code>devpassword</code></td>
<td>Mot de passe Redis</td>
</tr>
</tbody>
</table>
<h3>RabbitMQ</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>RABBITMQ_URL</code></td>
<td><code>amqp://veza:password@localhost:15672/</code></td>
<td>URL AMQP</td>
</tr>
<tr>
<td><code>RABBITMQ_DEFAULT_USER</code></td>
<td><code>veza</code></td>
<td>Utilisateur</td>
</tr>
<tr>
<td><code>RABBITMQ_DEFAULT_PASS</code></td>
<td><code>devpassword</code></td>
<td>Mot de passe</td>
</tr>
</tbody>
</table>
<h3>JWT &amp; Sécurité</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>JWT_SECRET</code></td>
<td><code>min-32-characters...</code></td>
<td>Secret JWT (fallback HS256)</td>
</tr>
<tr>
<td><code>JWT_PRIVATE_KEY_PATH</code></td>
<td><code>/path/to/jwt-private.pem</code></td>
<td>Clé privée RS256</td>
</tr>
<tr>
<td><code>JWT_PUBLIC_KEY_PATH</code></td>
<td><code>/path/to/jwt-public.pem</code></td>
<td>Clé publique RS256</td>
</tr>
<tr>
<td><code>JWT_ISSUER</code></td>
<td><code>veza-api</code></td>
<td>Émetteur JWT</td>
</tr>
<tr>
<td><code>JWT_AUDIENCE</code></td>
<td><code>veza-platform</code></td>
<td>Audience JWT</td>
</tr>
<tr>
<td><code>OAUTH_ENCRYPTION_KEY</code></td>
<td><code>&lt;32-byte-hex&gt;</code></td>
<td>Chiffrement OAuth</td>
</tr>
<tr>
<td><code>OAUTH_ALLOWED_REDIRECT_DOMAINS</code></td>
<td><code>https://veza.fr:5173</code></td>
<td>Domaines OAuth autorisés</td>
</tr>
<tr>
<td><code>CHAT_JWT_SECRET</code></td>
<td><code>&lt;différent de JWT_SECRET&gt;</code></td>
<td>Secret JWT chat</td>
</tr>
</tbody>
</table>
<h3>Stockage S3 (MinIO)</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>S3_ENABLED</code></td>
<td><code>true</code></td>
<td>Activer S3</td>
</tr>
<tr>
<td><code>S3_BUCKET</code></td>
<td><code>veza-uploads</code></td>
<td>Nom du bucket</td>
</tr>
<tr>
<td><code>S3_REGION</code></td>
<td><code>us-east-1</code></td>
<td>Région</td>
</tr>
<tr>
<td><code>S3_ENDPOINT</code></td>
<td><code>http://localhost:19000</code></td>
<td>Endpoint MinIO</td>
</tr>
<tr>
<td><code>S3_ACCESS_KEY</code></td>
<td><code>...</code></td>
<td>Clé d'accès</td>
</tr>
<tr>
<td><code>S3_SECRET_KEY</code></td>
<td><code>...</code></td>
<td>Clé secrète</td>
</tr>
</tbody>
</table>
<h3>Streaming &amp; Live</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>STREAM_HLS_BASE_URL</code></td>
<td><code>http://localhost:18083/live</code></td>
<td>URL base HLS</td>
</tr>
<tr>
<td><code>NGINX_RTMP_HOST</code></td>
<td><code>localhost</code></td>
<td>Hôte RTMP</td>
</tr>
<tr>
<td><code>RTMP_CALLBACK_SECRET</code></td>
<td><code>&lt;shared-secret&gt;</code></td>
<td>Secret callbacks RTMP</td>
</tr>
</tbody>
</table>
<h3>Elasticsearch</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ELASTICSEARCH_URL</code></td>
<td><code>http://localhost:19200</code></td>
<td>URL Elasticsearch</td>
</tr>
<tr>
<td><code>ELASTICSEARCH_AUTO_INDEX</code></td>
<td><code>true</code></td>
<td>Auto-indexation au démarrage</td>
</tr>
</tbody>
</table>
<h3>Antivirus</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ENABLE_CLAMAV</code></td>
<td><code>true</code></td>
<td>Activer le scan antivirus</td>
</tr>
<tr>
<td><code>CLAMAV_REQUIRED</code></td>
<td><code>true</code></td>
<td>Échouer si ClamAV indisponible</td>
</tr>
</tbody>
</table>
<h3>Paiements</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>STRIPE_CONNECT_ENABLED</code></td>
<td><code>true</code></td>
<td>Activer Stripe Connect</td>
</tr>
<tr>
<td><code>HYPERSWITCH_WEBHOOK_SECRET</code></td>
<td><code>&lt;secret&gt;</code></td>
<td>Secret webhook paiement</td>
</tr>
</tbody>
</table>
<h3>Logs &amp; Monitoring</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LOG_LEVEL</code></td>
<td><code>INFO</code></td>
<td>Niveau de log (DEBUG active les stack traces)</td>
</tr>
<tr>
<td><code>LOG_DIR</code></td>
<td><code>/var/log/veza</code></td>
<td>Répertoire des logs</td>
</tr>
<tr>
<td><code>LOG_FORMAT</code></td>
<td><code>json</code></td>
<td>Format (json en prod, text en dev)</td>
</tr>
<tr>
<td><code>SENTRY_DSN</code></td>
<td><code>https://...@ingest.sentry.io/0</code></td>
<td>DSN Sentry</td>
</tr>
<tr>
<td><code>SENTRY_ENVIRONMENT</code></td>
<td><code>development</code></td>
<td>Environnement Sentry</td>
</tr>
<tr>
<td><code>SENTRY_SAMPLE_RATE_ERRORS</code></td>
<td><code>1.0</code></td>
<td>Taux échantillonnage erreurs</td>
</tr>
<tr>
<td><code>SENTRY_SAMPLE_RATE_TRANSACTIONS</code></td>
<td><code>0.1</code></td>
<td>Taux échantillonnage transactions</td>
</tr>
<tr>
<td><code>PROMETHEUS_URL</code></td>
<td><code>http://prometheus:9090</code></td>
<td>URL Prometheus</td>
</tr>
</tbody>
</table>
<h3>Frontend</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Exemple</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VITE_API_URL</code></td>
<td><code>/api/v1</code></td>
<td>URL API (relative ou absolue)</td>
</tr>
<tr>
<td><code>VITE_USE_MSW</code></td>
<td><code>1</code></td>
<td>Activer mocks MSW</td>
</tr>
<tr>
<td><code>VITE_STORYBOOK</code></td>
<td><code>1</code></td>
<td>Mode Storybook</td>
</tr>
<tr>
<td><code>VITE_SENTRY_DSN</code></td>
<td><code>https://...</code></td>
<td>DSN Sentry frontend</td>
</tr>
</tbody>
</table>
<hr />
<h2>Docker Compose</h2>
<h3>Développement (<code>docker-compose.yml</code>)</h3>
<p>Services d'infrastructure pour le développement local :</p>
<table>
<thead>
<tr>
<th>Service</th>
<th>Image</th>
<th>Port exposé</th>
<th>Port interne</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>postgres</code></td>
<td>PostgreSQL 16</td>
<td>15432</td>
<td>5432</td>
</tr>
<tr>
<td><code>redis</code></td>
<td>Redis 7</td>
<td>16379</td>
<td>6379</td>
</tr>
<tr>
<td><code>rabbitmq</code></td>
<td>RabbitMQ 3</td>
<td>15672 (AMQP), 25672 (management)</td>
<td>5672, 15672</td>
</tr>
<tr>
<td><code>clamav</code></td>
<td>ClamAV 1.4</td>
<td>13310</td>
<td>3310</td>
</tr>
<tr>
<td><code>minio</code></td>
<td>MinIO</td>
<td>19000</td>
<td>9000</td>
</tr>
<tr>
<td><code>elasticsearch</code></td>
<td>Elasticsearch</td>
<td>19200</td>
<td>9200</td>
</tr>
</tbody>
</table>
<pre><code class="language-bash"># Lancer l'infrastructure
docker-compose up -d
# Ou via Makefile
make infra
</code></pre>
<h3>Production (<code>docker-compose.prod.yml</code>)</h3>
<p>Mêmes services avec :
- <strong>Volumes persistants</strong> pour les données
- <strong>Limites de ressources</strong> (CPU, mémoire)
- <strong>Health checks</strong> sur tous les services
- <strong>Configuration logging</strong> centralisée
- <strong>Isolation réseau</strong> entre services
- <strong>Restart policy</strong> : <code>unless-stopped</code></p>
<hr />
<h2>Dockerfiles des services applicatifs</h2>
<h3>Backend API (Go)</h3>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Base</th>
<th>Taille</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>veza-backend-api/Dockerfile</code></td>
<td><code>golang:1.21-alpine</code></td>
<td>~300 Mo</td>
<td>Développement</td>
</tr>
<tr>
<td><code>veza-backend-api/Dockerfile.production</code></td>
<td><code>scratch</code></td>
<td>~15 Mo</td>
<td>Production (binaire statique)</td>
</tr>
</tbody>
</table>
<h3>Stream Server (Rust)</h3>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Base</th>
<th>Taille</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>veza-stream-server/Dockerfile</code></td>
<td><code>rust:latest</code></td>
<td>~1 Go</td>
<td>Développement</td>
</tr>
<tr>
<td><code>veza-stream-server/Dockerfile.production</code></td>
<td><code>debian:bookworm-slim</code></td>
<td>~100 Mo</td>
<td>Production</td>
</tr>
</tbody>
</table>
<h3>Frontend (React)</h3>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Base</th>
<th>Taille</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>apps/web/Dockerfile</code></td>
<td><code>node:20-alpine</code><code>nginx:alpine</code></td>
<td>~50 Mo</td>
<td>Développement</td>
</tr>
<tr>
<td><code>apps/web/Dockerfile.production</code></td>
<td><code>nginx:latest</code></td>
<td>~30 Mo</td>
<td>Production</td>
</tr>
</tbody>
</table>
<hr />
<h2>Commandes Makefile</h2>
<pre><code class="language-bash"># Développement
make dev # Stack complète (backend Docker, web local)
make dev-full # Tous les services locaux + hot reload
make dev-web # Frontend seul
make dev-backend-api # Backend seul
make dev-stream-server # Stream server seul
# Infrastructure
make infra # docker-compose up -d
# Build
make build # Build tous les conteneurs
# Base de données
make migrate-up # Appliquer les migrations
make migrate-down # Rollback dernière migration
# Tests
make test # Tous les tests
make test-backend # Tests backend Go
make test-frontend # Tests frontend Vitest
make test-e2e # Tests end-to-end
</code></pre>
<hr />
<h2>Nginx (reverse proxy frontend)</h2>
<p>Fichier : <code>apps/web/nginx.conf</code></p>
<p>Configuration du reverse proxy en production :</p>
<pre><code>Client → Nginx (port 80/443)
├── / → fichiers statiques (SPA)
├── /api/* → Backend Go (port 8080)
├── /ws/* → Chat WebSocket (port 8081)
└── /stream/* → Stream Server Rust (port 8082)
</code></pre>
<p>Fonctionnalités :
- <strong>SPA fallback</strong> : Toutes les routes non-API renvoient <code>index.html</code>
- <strong>Gzip</strong> : Compression activée
- <strong>Cache</strong> : Headers pour assets statiques (JS, CSS, images)
- <strong>Sécurité</strong> : X-Frame-Options, X-Content-Type-Options</p>
<hr />
<h2>Différences dev / staging / production</h2>
<table>
<thead>
<tr>
<th>Aspect</th>
<th>Développement</th>
<th>Staging</th>
<th>Production</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>APP_ENV</code></td>
<td>development</td>
<td>staging</td>
<td>production</td>
</tr>
<tr>
<td>CORS</td>
<td>Wildcard <code>*</code></td>
<td>Domaines staging</td>
<td>Domaines stricts</td>
</tr>
<tr>
<td>CSRF</td>
<td>Optionnel (si Redis)</td>
<td>Obligatoire</td>
<td>Obligatoire</td>
</tr>
<tr>
<td>Swagger/docs</td>
<td>Activé</td>
<td>Activé</td>
<td><strong>Désactivé</strong></td>
</tr>
<tr>
<td>pprof debug</td>
<td>Activé</td>
<td>Désactivé</td>
<td><strong>Désactivé</strong></td>
</tr>
<tr>
<td>Logs</td>
<td>Texte, DEBUG</td>
<td>JSON, INFO</td>
<td>JSON, WARN+</td>
</tr>
<tr>
<td>Stack traces</td>
<td>Dans les réponses</td>
<td>Pas dans les réponses</td>
<td>Pas dans les réponses</td>
</tr>
<tr>
<td>Sentry</td>
<td>Optionnel</td>
<td>Activé</td>
<td>Activé</td>
</tr>
<tr>
<td>ClamAV</td>
<td>Optionnel</td>
<td>Recommandé</td>
<td><strong>Obligatoire</strong></td>
</tr>
<tr>
<td>Redis</td>
<td>Optionnel</td>
<td>Obligatoire</td>
<td><strong>Obligatoire</strong></td>
</tr>
<tr>
<td>Rate limiting</td>
<td>Souple</td>
<td>Modéré</td>
<td>Strict (DDoS)</td>
</tr>
</tbody>
</table>
<hr />
<h2>Documents liés</h2>
<ul>
<li>[[ARCHITECTURE_VEZA]] — Architecture globale</li>
<li>[[ROUTES_API]] — Endpoints API</li>
<li>[[SCHEMA_BASE_DE_DONNEES]] — Schéma PostgreSQL</li>
<li>[[SERVEUR_STREAMING_RUST]] — Serveur streaming</li>
<li>[[FRONTEND_REACT]] — Architecture frontend</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Community/Groupes_Chat/README.md</div>
</div>
<h1>Groupes &amp; Chat</h1>
<p>Module de <strong>messagerie communautaire</strong> pour groupes dutilisateurs : collaboration, entraide, salon projet.</p>
<h2>Objectifs :</h2>
<ul>
<li>Créer des espaces privés/publics de discussion.</li>
<li>Gérer les rôles, notifications, permissions.</li>
<li>Permettre lupload de fichiers légers (images, samples).</li>
</ul>
<h2>Contenu recommandé :</h2>
<ul>
<li>WebSocket ou SSE backend</li>
<li><code>rooms/</code>, <code>messages/</code>, <code>users/</code></li>
<li><code>permissions/</code> : modérateurs, membres</li>
<li>Web UI intégrable (chat component)</li>
</ul>
<blockquote>
<p>Inspiré de Discord/Mattermost, mais minimaliste.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Community/README.md</div>
</div>
<h1>Talas Community App Communautaire</h1>
<p>Ce dossier regroupe les composants de lapplication communautaire de Talas : partage, groupes, chat, échanges entre artistes.</p>
<h2>Objectifs :</h2>
<ul>
<li>Offrir une plateforme fluide pour les petits créateurs.</li>
<li>Favoriser les échanges, lentraide, la collaboration et la visibilité.</li>
<li>Permettre des extensions type forum, salon, recherche collaborateur.</li>
</ul>
<h2>Sous-sections :</h2>
<ul>
<li><code>Groupes_Chat/</code> : groupes privés/publics, messagerie</li>
<li><code>Partage/</code> : fichiers audio, presets, projets</li>
<li><code>Troc/</code> : plateforme de don, échange matériel ou services</li>
<li><code>Formation/</code> : espace de diffusion tutoriels, guides, lives</li>
</ul>
<blockquote>
<p>Peut évoluer vers un écosystème type SoundCloud + Discord + Coursera artisanal.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 FRONTEND_REACT.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/FRONTEND_REACT.md</div>
</div>
<h1>Architecture Frontend Veza (React)</h1>
<blockquote>
<p>Application web SPA + PWA avec support desktop Electron.
Source : <code>apps/web/src/</code></p>
</blockquote>
<h2>Stack technique</h2>
<table>
<thead>
<tr>
<th>Composant</th>
<th>Technologie</th>
<th>Version</th>
</tr>
</thead>
<tbody>
<tr>
<td>Framework UI</td>
<td>React</td>
<td>18</td>
</tr>
<tr>
<td>Langage</td>
<td>TypeScript</td>
<td>strict</td>
</tr>
<tr>
<td>Build</td>
<td>Vite</td>
<td>7.1.5</td>
</tr>
<tr>
<td>CSS</td>
<td>Tailwind CSS</td>
<td>v4</td>
</tr>
<tr>
<td>Composants</td>
<td>Radix UI + custom</td>
<td></td>
</tr>
<tr>
<td>État global</td>
<td>Zustand</td>
<td></td>
</tr>
<tr>
<td>Data fetching</td>
<td>TanStack Query (React Query)</td>
<td></td>
</tr>
<tr>
<td>Formulaires</td>
<td>React Hook Form + Zod</td>
<td></td>
</tr>
<tr>
<td>Routage</td>
<td>React Router</td>
<td>v6</td>
</tr>
<tr>
<td>i18n</td>
<td>i18next</td>
<td>EN/FR/ES</td>
</tr>
<tr>
<td>Tests unitaires</td>
<td>Vitest</td>
<td></td>
</tr>
<tr>
<td>Tests composants</td>
<td>Storybook</td>
<td></td>
</tr>
<tr>
<td>Desktop</td>
<td>Electron (wrapper)</td>
<td></td>
</tr>
</tbody>
</table>
<h2>Structure du projet</h2>
<pre><code>apps/web/src/
├── app/ # Composant App + initialisation
├── components/ # Composants UI (36 sous-dossiers)
│ ├── admin/ # Panel admin
│ ├── auth/ # Guards + flows auth
│ ├── commerce/ # Marketplace, checkout
│ ├── dashboard/ # Widgets dashboard
│ ├── feedback/ # Toasts, notifications
│ ├── layout/ # Sidebar, Header, MainLayout
│ ├── marketplace/ # Affichage produits, panier
│ ├── modals/ # Dialogues modaux
│ ├── player/ # Lecteur audio
│ ├── search/ # Recherche, filtres
│ ├── social/ # Likes, follows
│ ├── upload/ # Upload de fichiers
│ └── ui/ # Composants de base (19 sous-dossiers)
├── features/ # Modules fonctionnels (38 sous-dossiers)
│ ├── admin/ # Fonctionnalité admin
│ ├── analytics/ # Stats, graphiques
│ ├── auth/ # Flows d'authentification
│ ├── chat/ # Chat temps réel
│ ├── commerce/ # Logique marketplace
│ ├── developer/ # Outils développeur
│ ├── library/ # Bibliothèque musicale
│ ├── live/ # Streaming live
│ ├── marketplace/ # Marketplace
│ ├── notifications/ # Notifications
│ ├── playlists/ # Gestion playlists
│ ├── player/ # Logique lecteur audio
│ ├── profile/ # Profil utilisateur
│ ├── settings/ # Paramètres
│ ├── social/ # Social
│ ├── streaming/ # Streaming audio
│ └── tracks/ # Gestion pistes
├── hooks/ # Hooks React custom (46 fichiers)
├── router/ # Configuration routage
│ ├── routeConfig.tsx # Définition des routes
│ ├── AppRouter.tsx # Wrapper routeur
│ ├── ProtectedRoute.tsx # Guard authentification
│ └── PublicRoute.tsx # Wrapper pages publiques
├── services/ # Clients API &amp; logique métier
│ ├── api/ # Couche API REST
│ ├── websocket/ # Handlers WebSocket
│ └── storage/ # Storage local/session
├── stores/ # Stores Zustand
├── types/ # Types TypeScript (9 fichiers)
├── utils/ # Utilitaires (52 fichiers)
├── schemas/ # Schémas Zod (validation)
├── config/ # Configuration (env, URLs API)
├── context/ # Providers React Context
├── lib/ # Intégrations tierces
│ ├── i18n.ts # Configuration i18next
│ └── sentry.ts # Sentry error tracking
├── locales/ # Traductions i18n
├── styles/ # Styles globaux
└── __tests__/ # Tests
</code></pre>
<h2>Routes de l'application</h2>
<h3>Routes publiques (sans authentification)</h3>
<table>
<thead>
<tr>
<th>Route</th>
<th>Page</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/login</code></td>
<td>Login</td>
<td>Page de connexion</td>
</tr>
<tr>
<td><code>/register</code></td>
<td>Register</td>
<td>Inscription</td>
</tr>
<tr>
<td><code>/forgot-password</code></td>
<td>ForgotPassword</td>
<td>Récupération mot de passe</td>
</tr>
<tr>
<td><code>/verify-email</code></td>
<td>VerifyEmail</td>
<td>Vérification email</td>
</tr>
<tr>
<td><code>/reset-password</code></td>
<td>ResetPassword</td>
<td>Reset avec token</td>
</tr>
<tr>
<td><code>/</code></td>
<td>Landing</td>
<td>Page d'accueil</td>
</tr>
<tr>
<td><code>/discover</code></td>
<td>Discover</td>
<td>Découverte publique</td>
</tr>
<tr>
<td><code>/p/:playlistToken</code></td>
<td>SharedPlaylist</td>
<td>Playlist partagée</td>
</tr>
</tbody>
</table>
<h3>Routes protégées (authentification requise)</h3>
<table>
<thead>
<tr>
<th>Route</th>
<th>Page</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/dashboard</code></td>
<td>Dashboard</td>
<td>Feed principal</td>
</tr>
<tr>
<td><code>/library</code></td>
<td>Library</td>
<td>Bibliothèque musicale</td>
</tr>
<tr>
<td><code>/settings/*</code></td>
<td>Settings</td>
<td>Paramètres utilisateur</td>
</tr>
<tr>
<td><code>/u/:username</code></td>
<td>Profile</td>
<td>Profil utilisateur</td>
</tr>
<tr>
<td><code>/playlist/:id</code></td>
<td>PlaylistDetail</td>
<td>Détail playlist</td>
</tr>
<tr>
<td><code>/track/:id</code></td>
<td>TrackDetail</td>
<td>Détail piste</td>
</tr>
<tr>
<td><code>/marketplace/*</code></td>
<td>Marketplace</td>
<td>Pages marketplace</td>
</tr>
<tr>
<td><code>/cart</code></td>
<td>Cart</td>
<td>Panier</td>
</tr>
<tr>
<td><code>/checkout</code></td>
<td>Checkout</td>
<td>Paiement</td>
</tr>
<tr>
<td><code>/chat</code></td>
<td>Chat</td>
<td>Interface de chat</td>
</tr>
<tr>
<td><code>/notifications</code></td>
<td>Notifications</td>
<td>Notifications</td>
</tr>
<tr>
<td><code>/analytics</code></td>
<td>Analytics</td>
<td>Dashboard analytics</td>
</tr>
<tr>
<td><code>/webhooks</code></td>
<td>Webhooks</td>
<td>Gestion webhooks</td>
</tr>
<tr>
<td><code>/admin/*</code></td>
<td>Admin</td>
<td>Panel admin</td>
</tr>
<tr>
<td><code>/search</code></td>
<td>Search</td>
<td>Résultats recherche</td>
</tr>
<tr>
<td><code>/social/*</code></td>
<td>Social</td>
<td>Fonctionnalités sociales</td>
</tr>
<tr>
<td><code>/live/*</code></td>
<td>Live</td>
<td>Streaming live</td>
</tr>
<tr>
<td><code>/seller/*</code></td>
<td>Seller</td>
<td>Dashboard vendeur</td>
</tr>
<tr>
<td><code>/developer/*</code></td>
<td>Developer</td>
<td>Outils développeur</td>
</tr>
<tr>
<td><code>/queue</code></td>
<td>Queue</td>
<td>File d'attente</td>
</tr>
<tr>
<td><code>/listen-together</code></td>
<td>CoListening</td>
<td>Co-écoute</td>
</tr>
<tr>
<td><code>/wishlist</code></td>
<td>Wishlist</td>
<td>Liste de souhaits</td>
</tr>
<tr>
<td><code>/purchases</code></td>
<td>Purchases</td>
<td>Historique achats</td>
</tr>
</tbody>
</table>
<h2>Gestion d'état (Zustand)</h2>
<h3>Stores principaux</h3>
<pre><code class="language-typescript">// UI State (stores/ui.ts)
{
sidebarOpen: boolean,
darkMode: boolean,
activeModal: string | null,
}
// Library State (stores/library.ts)
{
favorites: Track[],
recentlyPlayed: Track[],
playlists: Playlist[],
}
// Cart State (stores/cartStore.ts)
{
items: CartItem[],
total: number,
}
// Rate Limit State (stores/rateLimit.ts)
{
remaining: number,
resetTime: Date,
}
</code></pre>
<h2>Services API (<code>services/api/</code>)</h2>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Domaine</th>
<th>Exemples d'opérations</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>auth.ts</code></td>
<td>Authentification</td>
<td>login, register, refresh, logout</td>
</tr>
<tr>
<td><code>tracks.ts</code></td>
<td>Pistes</td>
<td>CRUD, upload, search, like</td>
</tr>
<tr>
<td><code>playlists.ts</code></td>
<td>Playlists</td>
<td>CRUD, collaborators, share</td>
</tr>
<tr>
<td><code>users.ts</code></td>
<td>Utilisateurs</td>
<td>profil, follow, block, settings</td>
</tr>
<tr>
<td><code>marketplace.ts</code></td>
<td>Marketplace</td>
<td>products, orders, reviews</td>
</tr>
<tr>
<td><code>search.ts</code></td>
<td>Recherche</td>
<td>unified search, suggestions</td>
</tr>
<tr>
<td><code>streaming.ts</code></td>
<td>Streaming</td>
<td>intégration stream server</td>
</tr>
<tr>
<td><code>chat.ts</code></td>
<td>Chat</td>
<td>rooms, messages, reactions</td>
</tr>
<tr>
<td><code>notifications.ts</code></td>
<td>Notifications</td>
<td>list, preferences, push</td>
</tr>
<tr>
<td><code>analytics.ts</code></td>
<td>Analytics</td>
<td>stats, charts, export</td>
</tr>
</tbody>
</table>
<h2>Hooks personnalisés (<code>hooks/</code>)</h2>
<table>
<thead>
<tr>
<th>Hook</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>useUser()</code></td>
<td>Données utilisateur courant</td>
</tr>
<tr>
<td><code>useAuth()</code></td>
<td>État d'authentification</td>
</tr>
<tr>
<td><code>useTracks()</code></td>
<td>Opérations sur les pistes</td>
</tr>
<tr>
<td><code>usePlaylists()</code></td>
<td>Opérations sur les playlists</td>
</tr>
<tr>
<td><code>usePlayer()</code></td>
<td>État du lecteur audio</td>
</tr>
<tr>
<td><code>useSearch()</code></td>
<td>Fonctionnalité recherche</td>
</tr>
<tr>
<td><code>useChat()</code></td>
<td>Opérations chat</td>
</tr>
<tr>
<td><code>useNotifications()</code></td>
<td>État notifications</td>
</tr>
<tr>
<td><code>useLocalStorage()</code></td>
<td>Persistance locale</td>
</tr>
<tr>
<td><code>useQueryClient()</code></td>
<td>Intégration TanStack Query</td>
</tr>
</tbody>
</table>
<p>46 hooks au total pour encapsuler la logique métier.</p>
<h2>Configuration</h2>
<pre><code class="language-typescript">// config/env.ts
export const env = {
API_URL: import.meta.env.VITE_API_URL || '/api/v1',
FRONTEND_URL: 'http://localhost:5173',
USE_MSW: import.meta.env.VITE_USE_MSW === 'true',
STORYBOOK: import.meta.env.VITE_STORYBOOK === 'true',
SENTRY_DSN: import.meta.env.VITE_SENTRY_DSN,
}
</code></pre>
<h3>Variables d'environnement frontend</h3>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>VITE_API_URL</code></td>
<td>URL de l'API backend</td>
</tr>
<tr>
<td><code>VITE_USE_MSW</code></td>
<td>Activer les mocks MSW</td>
</tr>
<tr>
<td><code>VITE_STORYBOOK</code></td>
<td>Mode Storybook</td>
</tr>
<tr>
<td><code>VITE_SENTRY_DSN</code></td>
<td>DSN Sentry pour error tracking</td>
</tr>
</tbody>
</table>
<h2>Internationalisation (i18n)</h2>
<ul>
<li><strong>Bibliothèque</strong> : i18next</li>
<li><strong>Langues</strong> : Anglais (EN), Français (FR), Espagnol (ES)</li>
<li><strong>Fichiers</strong> : <code>src/locales/</code></li>
<li><strong>Détection</strong> : Langue du navigateur automatique</li>
<li><strong>Persistance</strong> : localStorage</li>
</ul>
<h2>Scripts de développement</h2>
<pre><code class="language-bash"># Développement avec hot reload
npm run dev # ou : make dev-web
# Développement avec API mockée (MSW)
npm run dev:mocks
# Build production
npm run build
# Tests
npm run test # Vitest
npm run test:e2e # E2E (au root du repo)
# Qualité
npm run lint # ESLint
npm run typecheck # TypeScript strict
# Storybook (catalogue composants)
npm run storybook # Port 6006
npm run build-storybook
</code></pre>
<h2>Nginx (production)</h2>
<p>En production, le frontend est servi par Nginx avec :
- <strong>SPA routing</strong> : Toutes les routes renvoient vers <code>index.html</code>
- <strong>Proxy <code>/api</code></strong> → Backend Go (port 8080)
- <strong>Proxy <code>/ws</code></strong> → Chat WebSocket (port 8081)
- <strong>Proxy <code>/stream</code></strong> → Stream server (port 8082)
- <strong>Compression gzip</strong> activée
- <strong>Cache headers</strong> pour les assets statiques
- <strong>Security headers</strong> : X-Frame-Options, X-Content-Type-Options</p>
<p>Configuration : <code>apps/web/nginx.conf</code></p>
<h2>Dockerfiles</h2>
<table>
<thead>
<tr>
<th>Fichier</th>
<th>Base</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Dockerfile</code></td>
<td><code>node:20-alpine</code><code>nginx:alpine</code></td>
<td>Développement</td>
</tr>
<tr>
<td><code>Dockerfile.production</code></td>
<td><code>nginx:latest</code></td>
<td>Production</td>
</tr>
</tbody>
</table>
<h2>Desktop (Electron)</h2>
<p>Le dossier <code>veza-desktop/</code> contient un wrapper Electron minimal qui charge <code>apps/web</code>. Pas d'application native séparée — le même code frontend est utilisé partout.</p>
<h2>Statistiques</h2>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Valeur</th>
</tr>
</thead>
<tbody>
<tr>
<td>Composants UI</td>
<td>661</td>
</tr>
<tr>
<td>Routes</td>
<td>52+</td>
</tr>
<tr>
<td>Hooks custom</td>
<td>46</td>
</tr>
<tr>
<td>Utilitaires</td>
<td>52</td>
</tr>
<tr>
<td>Sous-dossiers features</td>
<td>38</td>
</tr>
<tr>
<td>Langues i18n</td>
<td>3 (EN/FR/ES)</td>
</tr>
</tbody>
</table>
<h2>Documents liés</h2>
<ul>
<li>[[ARCHITECTURE_VEZA]] — Architecture globale</li>
<li>[[ROUTES_API]] — Endpoints API backend</li>
<li>[[CONFIGURATION_ENVIRONNEMENT]] — Variables d'environnement</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Personal/README.md</div>
</div>
<h1>Talas Personal Application Utilisateur</h1>
<p>Ce dossier contient lapplication personnelle Talas : gestion des produits achetés, plugins hébergés, fichiers audio, presets favoris.</p>
<h2>Objectifs :</h2>
<ul>
<li>Fournir à chaque artiste un “atelier personnel” connecté à Talas.</li>
<li>Centraliser docs, garanties, ressources, historique dusage.</li>
<li>Intégrer des outils avancés (AudioGridder, presets, favoris cloud).</li>
</ul>
<h2>Sous-dossiers :</h2>
<ul>
<li><code>AudioGridder_Client/</code> : serveur distant de plugins</li>
<li><code>Cloud_Perso/</code> : fichiers audio, presets, snapshots</li>
<li><code>Produits_Personnels/</code> : garanties, docs, historique</li>
</ul>
<blockquote>
<p>Tu peux l'étendre vers un "OS musical personnel" avec interopérabilité.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/README.md</div>
</div>
<h1>03_APPS_&amp;_SERVICES Développement Applicatif</h1>
<p>Contient toutes les spécifications techniques de la plateforme <strong>Veza</strong> : architecture, API, schéma de données, streaming, frontend et configuration.</p>
<p>Le code source vit dans <code>/home/senke/git/talas/veza/</code> — ce dossier ne contient que la <strong>documentation technique</strong>.</p>
<h2>Documents de référence</h2>
<table>
<thead>
<tr>
<th>Document</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>[[ARCHITECTURE_VEZA]]</td>
<td>Architecture globale (3 services, stack, flux de données)</td>
</tr>
<tr>
<td>[[ROUTES_API]]</td>
<td>Référence des 500+ endpoints REST et WebSocket</td>
</tr>
<tr>
<td>[[SCHEMA_BASE_DE_DONNEES]]</td>
<td>60+ tables PostgreSQL, relations, Redis</td>
</tr>
<tr>
<td>[[SERVEUR_STREAMING_RUST]]</td>
<td>Serveur Axum : HLS adaptatif, WebSocket, transcoding</td>
</tr>
<tr>
<td>[[FRONTEND_REACT]]</td>
<td>React 18, 661 composants, 52+ routes, Zustand</td>
</tr>
<tr>
<td>[[CONFIGURATION_ENVIRONNEMENT]]</td>
<td>Docker, variables denv, différences dev/staging/prod</td>
</tr>
</tbody>
</table>
<h2>Modules fonctionnels</h2>
<ul>
<li><code>Auth_&amp;_Core/</code> : inscription, login, sessions JWT RS256, RBAC, MFA, WebAuthn/Passkeys</li>
<li><code>Shop/</code> : marketplace, panier, paiements Stripe Connect, KYC, royalties</li>
<li><code>Community/</code> : social (feed, groupes, likes), chat temps réel, co-écoute</li>
<li><code>Personal/</code> : cloud personnel, inventaire équipement, file dattente</li>
<li><code>APIs_&amp;_Rust_Modules/</code> : routes API détaillées, serveur streaming Rust</li>
</ul>
<h2>Stack résumée</h2>
<ul>
<li><strong>Backend</strong> : Go (Gin, GORM) — 500+ endpoints, 68 modèles, 115 migrations SQL</li>
<li><strong>Streaming</strong> : Rust (Axum) — HLS adaptatif, WebSocket, transcoding FFmpeg</li>
<li><strong>Frontend</strong> : React 18, TypeScript, Tailwind v4, Zustand, TanStack Query</li>
<li><strong>Infra</strong> : PostgreSQL 16, Redis 7, RabbitMQ 3, Elasticsearch, MinIO S3, ClamAV</li>
</ul>
<h2>Connexions transversales</h2>
<ul>
<li>[[04_INFRA_DEPLOIEMENT]] (CI/CD, conteneurisation, serveurs)</li>
<li>[[05_EXPERIENCE_UTILISATEUR]] (interface + feedback UX)</li>
<li>[[06_COMMUNAUTE_ECOSYSTEME]] (fonctions communautaires)</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 SCHEMA_BASE_DE_DONNEES.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/SCHEMA_BASE_DE_DONNEES.md</div>
</div>
<h1>Schéma Base de Données Veza</h1>
<blockquote>
<p>Référence complète du schéma PostgreSQL — 60+ tables, UUIDs, soft deletes.
Source : <code>veza-backend-api/internal/models/</code> (68 fichiers Go) et <code>migrations/</code> (115 fichiers SQL)
ORM : GORM v1 (mapping uniquement, migrations 100% SQL)</p>
</blockquote>
<h2>Stratégie de migration</h2>
<ul>
<li><strong>Migrations SQL pures</strong> dans <code>veza-backend-api/migrations/</code> (001 à 115+)</li>
<li><strong>GORM</strong> utilisé uniquement pour le mapping Go ↔ PostgreSQL, pas pour la création de schéma</li>
<li><strong>AutoMigrate()</strong> est vide — tout est géré par SQL</li>
<li><strong>Outil</strong> : <code>go run ./cmd/migrate_tool/main.go up</code> (ou <code>down</code> pour rollback)</li>
<li>Table <code>schema_migrations</code> pour le suivi des migrations appliquées</li>
</ul>
<h2>Conventions</h2>
<ul>
<li><strong>Clés primaires</strong> : UUID v4 (<code>uuid.UUID</code> avec <code>google/uuid</code>)</li>
<li><strong>Soft delete</strong> : Colonne <code>deleted_at</code> indexée sur 20+ tables</li>
<li><strong>Timestamps</strong> : <code>created_at</code>, <code>updated_at</code> sur toutes les tables</li>
<li><strong>JSONB</strong> : Utilisé pour les données flexibles (social_links, specs, metadata)</li>
<li><strong>Enums</strong> : Types PostgreSQL custom (user_role, track_status, etc.)</li>
<li><strong>Foreign keys</strong> : Contraintes PostgreSQL natives, toujours actives</li>
</ul>
<hr />
<h2>A. Utilisateurs &amp; Authentification</h2>
<h3><code>users</code></h3>
<p>Table centrale — profil utilisateur.</p>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>username</code></td>
<td>VARCHAR(30)</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>email</code></td>
<td>VARCHAR</td>
<td>NOT NULL, UNIQUE</td>
<td></td>
</tr>
<tr>
<td><code>password_hash</code></td>
<td>VARCHAR</td>
<td>NOT NULL</td>
<td>Masqué en JSON</td>
</tr>
<tr>
<td><code>token_version</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td>Pour révocation de tokens</td>
</tr>
<tr>
<td><code>first_name</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>last_name</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>avatar</code></td>
<td>TEXT</td>
<td></td>
<td>URL avatar</td>
</tr>
<tr>
<td><code>banner_url</code></td>
<td>TEXT</td>
<td></td>
<td>URL bannière</td>
</tr>
<tr>
<td><code>bio</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>location</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>birthdate</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>gender</code></td>
<td>VARCHAR(20)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>role</code></td>
<td>user_role ENUM</td>
<td>DEFAULT 'user'</td>
<td></td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
<tr>
<td><code>is_verified</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>is_banned</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>is_admin</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>is_public</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
<tr>
<td><code>last_login_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>login_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>password_changed_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>social_links</code></td>
<td>JSONB</td>
<td>DEFAULT '{}'</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td>INDEX</td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>roles</code> (M2M via <code>user_roles</code>), <code>track_likes</code> (has many, CASCADE)</p>
<h3><code>sessions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>token_hash</code></td>
<td>VARCHAR</td>
<td>UNIQUE INDEX</td>
<td>Token hashé</td>
</tr>
<tr>
<td><code>ip_address</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>user_agent</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>revoked_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>refresh_tokens</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), NOT NULL, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>token_hash</code></td>
<td>VARCHAR</td>
<td>NOT NULL, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<h3><code>roles</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(50)</td>
<td>UNIQUE, NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>display_name</code></td>
<td>VARCHAR(100)</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_system</code></td>
<td>BOOL</td>
<td></td>
<td>Rôle système (non supprimable)</td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>users</code> (M2M), <code>permissions</code> (M2M)</p>
<h3><code>permissions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(100)</td>
<td>UNIQUE, NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>resource</code></td>
<td>VARCHAR(50)</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>action</code></td>
<td>VARCHAR(50)</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>user_roles</code> (table pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), UNIQUE(user_id, role_id)</td>
<td></td>
</tr>
<tr>
<td><code>role_id</code></td>
<td>UUID</td>
<td>FK → roles (CASCADE)</td>
<td></td>
</tr>
<tr>
<td><code>role</code></td>
<td>VARCHAR(50)</td>
<td></td>
<td>Nom du rôle (dénormalisé)</td>
</tr>
<tr>
<td><code>assigned_at</code></td>
<td>TIMESTAMP</td>
<td>DEFAULT CURRENT_TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>assigned_by</code></td>
<td>UUID</td>
<td>NULLABLE, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>role_permissions</code> (table pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>role_id</code></td>
<td>UUID</td>
<td>FK → roles (CASCADE), UNIQUE(role_id, permission_id)</td>
</tr>
<tr>
<td><code>permission_id</code></td>
<td>UUID</td>
<td>FK → permissions (CASCADE)</td>
</tr>
</tbody>
</table>
<h3><code>mfa_configs</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>NOT NULL, UNIQUE INDEX</td>
<td></td>
</tr>
<tr>
<td><code>secret</code></td>
<td>VARCHAR</td>
<td>NOT NULL</td>
<td>Masqué en JSON</td>
</tr>
<tr>
<td><code>backup_codes</code></td>
<td>TEXT</td>
<td></td>
<td>JSON array</td>
</tr>
<tr>
<td><code>is_enabled</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>last_used_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<h3><code>recovery_codes</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>code</code></td>
<td>VARCHAR</td>
<td>Masqué</td>
</tr>
<tr>
<td><code>is_used</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
</tr>
<tr>
<td><code>used_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>webauthn_credentials</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>credential_id</code></td>
<td>BYTEA</td>
<td>UNIQUE, NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>public_key</code></td>
<td>BYTEA</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>attestation_type</code></td>
<td>VARCHAR(50)</td>
<td>DEFAULT 'none'</td>
<td></td>
</tr>
<tr>
<td><code>aaguid</code></td>
<td>BYTEA</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>sign_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(100)</td>
<td>DEFAULT 'My Passkey'</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>last_used_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>federated_identities</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>provider</code></td>
<td>VARCHAR</td>
<td></td>
<td>google, github, facebook, twitter</td>
</tr>
<tr>
<td><code>provider_id</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>email</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>display_name</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>avatar_url</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>access_token</code></td>
<td>TEXT</td>
<td></td>
<td>Masqué</td>
</tr>
<tr>
<td><code>refresh_token</code></td>
<td>TEXT</td>
<td></td>
<td>Masqué</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>api_keys</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(100)</td>
<td></td>
</tr>
<tr>
<td><code>prefix</code></td>
<td>VARCHAR(16)</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>hashed_key</code></td>
<td>VARCHAR(128)</td>
<td>Masqué</td>
</tr>
<tr>
<td><code>scopes</code></td>
<td>TEXT[]</td>
<td>PostgreSQL array</td>
</tr>
<tr>
<td><code>last_used_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>user_settings</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>email_notifications</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>push_notifications</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>browser_notifications</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_on_follow</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_on_like</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_on_comment</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_on_message</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_on_mention</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>email_marketing</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>allow_search_indexing</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>show_activity</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>explicit_content</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>autoplay</code></td>
<td>BOOL</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>user_profiles</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>language</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'en'</td>
</tr>
<tr>
<td><code>timezone</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'UTC'</td>
</tr>
<tr>
<td><code>theme</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'auto'</td>
</tr>
</tbody>
</table>
<h3><code>user_presence</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'offline'</td>
<td>online/away/busy/offline</td>
</tr>
<tr>
<td><code>last_seen_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>status_message</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
<td>Piste en écoute</td>
</tr>
<tr>
<td><code>track_title</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>invisible</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>B. Pistes audio</h2>
<h3><code>tracks</code></h3>
<p>Table principale des pistes audio.</p>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>file_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>title</code></td>
<td>VARCHAR(255)</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>artist</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>album</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>duration</code></td>
<td>INT</td>
<td></td>
<td>Secondes</td>
</tr>
<tr>
<td><code>genre</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>tags</code></td>
<td>TEXT[]</td>
<td></td>
<td>PostgreSQL array</td>
</tr>
<tr>
<td><code>year</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>bpm</code></td>
<td>INT</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>musical_key</code></td>
<td>VARCHAR(10)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>file_path</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>file_size</code></td>
<td>BIGINT</td>
<td></td>
<td>Octets</td>
</tr>
<tr>
<td><code>format</code></td>
<td>VARCHAR(10)</td>
<td></td>
<td>wav, mp3, flac...</td>
</tr>
<tr>
<td><code>bitrate</code></td>
<td>INT</td>
<td></td>
<td>kbps</td>
</tr>
<tr>
<td><code>sample_rate</code></td>
<td>INT</td>
<td></td>
<td>Hz</td>
</tr>
<tr>
<td><code>waveform_path</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>waveform_url</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>cover_art_path</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_public</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>track_status ENUM</td>
<td></td>
<td>uploading/processing/completed/failed</td>
</tr>
<tr>
<td><code>status_message</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>stream_status</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'pending'</td>
<td>pending/processing/ready/error</td>
</tr>
<tr>
<td><code>stream_manifest_url</code></td>
<td>VARCHAR</td>
<td></td>
<td>URL playlist HLS</td>
</tr>
<tr>
<td><code>play_count</code></td>
<td>BIGINT</td>
<td></td>
<td>Non exposé en API</td>
</tr>
<tr>
<td><code>like_count</code></td>
<td>BIGINT</td>
<td></td>
<td>Non exposé en API</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>updated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td>INDEX</td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>user</code> (belongs to), <code>playlists</code> (M2M), <code>likes</code> (has many), <code>shares</code> (has many), <code>versions</code> (has many), <code>hls_streams</code> (has many)</p>
<h3><code>track_versions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), UNIQUE(track_id, version_number)</td>
</tr>
<tr>
<td><code>version_number</code></td>
<td>INT</td>
<td></td>
</tr>
<tr>
<td><code>file_path</code></td>
<td>VARCHAR(500)</td>
<td></td>
</tr>
<tr>
<td><code>file_size</code></td>
<td>BIGINT</td>
<td>Octets</td>
</tr>
<tr>
<td><code>changelog</code></td>
<td>TEXT</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>track_likes</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), UNIQUE(user_id, track_id)</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE)</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>track_plays</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (SET NULL), NULLABLE, INDEX</td>
<td>Anonyme si non connecté</td>
</tr>
<tr>
<td><code>duration</code></td>
<td>INT</td>
<td></td>
<td>Secondes jouées</td>
</tr>
<tr>
<td><code>played_at</code></td>
<td>TIMESTAMP</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>device</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>ip_address</code></td>
<td>VARCHAR(45)</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>track_shares</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>share_token</code></td>
<td>VARCHAR(255)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>permissions</code></td>
<td>VARCHAR(50)</td>
<td>DEFAULT 'read' (read/download)</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>access_count</code></td>
<td>BIGINT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>track_comments</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>parent_id</code></td>
<td>UUID</td>
<td>FK → track_comments (CASCADE), NULLABLE</td>
<td>Réponses</td>
</tr>
<tr>
<td><code>content</code></td>
<td>TEXT</td>
<td>NOT NULL</td>
<td></td>
</tr>
<tr>
<td><code>timestamp</code></td>
<td>FLOAT</td>
<td></td>
<td>Position en secondes</td>
</tr>
<tr>
<td><code>is_edited</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>parent</code> (self-ref), <code>replies</code> (has many self-ref)</p>
<h3><code>track_reposts</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td>INDEX</td>
</tr>
</tbody>
</table>
<h3><code>track_stems</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(100)</td>
<td></td>
</tr>
<tr>
<td><code>file_path</code></td>
<td>VARCHAR(500)</td>
<td></td>
</tr>
<tr>
<td><code>format</code></td>
<td>VARCHAR(10)</td>
<td></td>
</tr>
<tr>
<td><code>size_bytes</code></td>
<td>BIGINT</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<h3><code>track_lyrics</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), UNIQUE</td>
</tr>
<tr>
<td><code>content</code></td>
<td>TEXT</td>
<td>NOT NULL</td>
</tr>
</tbody>
</table>
<h3><code>track_history</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (SET NULL)</td>
<td></td>
</tr>
<tr>
<td><code>action</code></td>
<td>ENUM</td>
<td>INDEX</td>
<td>created/updated/deleted/published/unpublished/restored</td>
</tr>
<tr>
<td><code>old_value</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>new_value</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>tags</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(30)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>use_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>track_tags</code> (pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>tag_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>genres</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>slug</code></td>
<td>VARCHAR(50)</td>
<td>PK</td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(100)</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>track_genres</code> (pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>genre_slug</code></td>
<td>VARCHAR(50)</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>position</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>playback_analytics</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>play_time</code></td>
<td>INT</td>
<td></td>
<td>Secondes</td>
</tr>
<tr>
<td><code>pause_count</code></td>
<td>INT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>seek_count</code></td>
<td>INT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>completion_rate</code></td>
<td>DECIMAL(5,2)</td>
<td></td>
<td>0-100%</td>
</tr>
<tr>
<td><code>started_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>ended_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>C. Playlists</h2>
<h3><code>playlists</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(200)</td>
<td></td>
<td>Titre (colonne DB: <code>name</code>)</td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_public</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
<tr>
<td><code>cover_url</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>track_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>follower_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>is_editorial</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td>Playlist éditoriale</td>
</tr>
<tr>
<td><code>is_default_favorites</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td>Playlist favoris auto</td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>user</code> (belongs to), <code>tracks</code> (has many PlaylistTrack), <code>collaborators</code> (has many, CASCADE)</p>
<h3><code>playlist_tracks</code> (pivot ordonné)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>playlist_id</code></td>
<td>UUID</td>
<td>FK → playlists (CASCADE), INDEX</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
</tr>
<tr>
<td><code>position</code></td>
<td>INT</td>
<td>Ordre dans la playlist</td>
</tr>
<tr>
<td><code>added_by</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>added_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>playlist_collaborators</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>playlist_id</code></td>
<td>UUID</td>
<td>FK → playlists, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users, INDEX</td>
<td></td>
</tr>
<tr>
<td><code>permission</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'read'</td>
<td>read/write/admin</td>
</tr>
</tbody>
</table>
<h3><code>playlist_follows</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>playlist_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
</tbody>
</table>
<h3><code>playlist_share_links</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>playlist_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>share_token</code></td>
<td>VARCHAR(255)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>access_count</code></td>
<td>BIGINT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>playlist_versions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>playlist_id</code></td>
<td>UUID</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (SET NULL)</td>
<td></td>
</tr>
<tr>
<td><code>version</code></td>
<td>INT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>action</code></td>
<td>ENUM</td>
<td></td>
<td>Type de modification</td>
</tr>
<tr>
<td><code>title</code></td>
<td>VARCHAR(200)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_public</code></td>
<td>BOOL</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>cover_url</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>tracks_snapshot</code></td>
<td>TEXT</td>
<td></td>
<td>JSON des pistes à cette version</td>
</tr>
</tbody>
</table>
<h3><code>user_genre_follows</code> (pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>genre_slug</code></td>
<td>VARCHAR</td>
<td>PK composite</td>
</tr>
</tbody>
</table>
<h3><code>user_tag_follows</code> (pivot)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
<tr>
<td><code>tag_id</code></td>
<td>UUID</td>
<td>PK composite</td>
</tr>
</tbody>
</table>
<hr />
<h2>D. Chat &amp; Messagerie</h2>
<h3><code>rooms</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>room_type</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'public'</td>
<td>public/private/dm</td>
</tr>
<tr>
<td><code>is_private</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE)</td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>members</code> (has many, CASCADE), <code>messages</code> (has many, CASCADE)</p>
<h3><code>room_members</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>room_id</code></td>
<td>UUID</td>
<td>FK → rooms (CASCADE)</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE)</td>
</tr>
<tr>
<td><code>role</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'member'</td>
</tr>
<tr>
<td><code>joined_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>room_invitations</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>room_id</code></td>
<td>UUID</td>
<td>FK → rooms</td>
</tr>
<tr>
<td><code>inviter_id</code></td>
<td>UUID</td>
<td>FK → users</td>
</tr>
<tr>
<td><code>invitee_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>token</code></td>
<td>UUID</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'pending' (pending/accepted/expired)</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>messages</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>room_id</code></td>
<td>UUID</td>
<td>FK → rooms</td>
<td></td>
</tr>
<tr>
<td><code>sender_id</code></td>
<td>UUID</td>
<td>FK → users</td>
<td></td>
</tr>
<tr>
<td><code>content</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>message_type</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'text'</td>
<td>text/image/audio/system</td>
</tr>
<tr>
<td><code>reply_to_id</code></td>
<td>UUID</td>
<td>NULLABLE, self-ref</td>
<td>Réponse à</td>
</tr>
<tr>
<td><code>is_edited</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>is_deleted</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>is_pinned</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>edited_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'sent'</td>
<td></td>
</tr>
<tr>
<td><code>metadata</code></td>
<td>JSONB</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>content_tsv</code></td>
<td>TSVECTOR</td>
<td></td>
<td>Recherche full-text</td>
</tr>
</tbody>
</table>
<h3><code>message_reactions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE(user_id, message_id, emoji)</td>
</tr>
<tr>
<td><code>message_id</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>emoji</code></td>
<td>VARCHAR(50)</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>read_receipts</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE(user_id, message_id)</td>
</tr>
<tr>
<td><code>message_id</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>read_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>delivered_status</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE(user_id, message_id)</td>
</tr>
<tr>
<td><code>message_id</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>delivered_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>E. Streaming &amp; Collaboration</h2>
<h3><code>hls_streams</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>playlist_url</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>segments_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>bitrates</code></td>
<td>JSONB</td>
<td></td>
<td>Liste des bitrates disponibles</td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'pending', INDEX</td>
<td>pending/processing/ready/failed</td>
</tr>
</tbody>
</table>
<h3><code>hls_transcode_queue</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>priority</code></td>
<td>INT</td>
<td>DEFAULT 5</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'pending', INDEX</td>
<td>pending/processing/completed/failed</td>
</tr>
<tr>
<td><code>retry_count</code></td>
<td>INT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>max_retries</code></td>
<td>INT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>error_message</code></td>
<td>TEXT</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>started_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>completed_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>live_streams</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE)</td>
<td></td>
</tr>
<tr>
<td><code>title</code></td>
<td>VARCHAR(200)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>category</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>thumbnail_url</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>stream_key</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td>Masqué en JSON</td>
</tr>
<tr>
<td><code>streamer_name</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_live</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>started_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>ended_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>viewer_count</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>tags</code></td>
<td>JSONB</td>
<td>DEFAULT '[]'</td>
<td></td>
</tr>
<tr>
<td><code>scheduled_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>stream_url</code></td>
<td>TEXT</td>
<td>DEFAULT ''</td>
<td></td>
</tr>
<tr>
<td><code>is_vod</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<h3><code>co_listening_sessions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>host_id</code></td>
<td>UUID</td>
<td>FK → users, INDEX</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks, INDEX</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>queues</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE</td>
<td></td>
</tr>
<tr>
<td><code>current_track_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>current_position</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>is_playing</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>shuffle</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td></td>
</tr>
<tr>
<td><code>repeat_mode</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'off'</td>
<td>off/one/all</td>
</tr>
<tr>
<td><code>volume</code></td>
<td>INT</td>
<td>DEFAULT 100</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>queue_items</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>queue_id</code></td>
<td>UUID</td>
<td>FK → queues</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td>FK → tracks</td>
</tr>
<tr>
<td><code>position</code></td>
<td>INT</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>queue_sessions</code> (sessions collaboratives)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>share_token</code></td>
<td>VARCHAR(32)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
</tbody>
</table>
<h3><code>shared_queue_items</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>session_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>track_id</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>position</code></td>
<td>INT</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>F. Fichiers &amp; Stockage</h2>
<h3><code>user_files</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users</td>
<td></td>
</tr>
<tr>
<td><code>folder_id</code></td>
<td>UUID</td>
<td>FK → user_folders (SET NULL), NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>filename</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>s3_key</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td>Clé MinIO/S3</td>
</tr>
<tr>
<td><code>size_bytes</code></td>
<td>BIGINT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
<tr>
<td><code>mime_type</code></td>
<td>VARCHAR(100)</td>
<td>DEFAULT 'application/octet-stream'</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>user_folders</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE)</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>parent_id</code></td>
<td>UUID</td>
<td>FK → user_folders (CASCADE), NULLABLE</td>
<td>Hiérarchie</td>
</tr>
</tbody>
</table>
<p><strong>Relations</strong> : <code>parent</code> (self-ref), <code>children</code> (has many), <code>files</code> (has many)</p>
<h3><code>cloud_file_versions</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>file_id</code></td>
<td>UUID</td>
<td>FK → user_files (CASCADE)</td>
</tr>
<tr>
<td><code>version</code></td>
<td>INT</td>
<td></td>
</tr>
<tr>
<td><code>storage_key</code></td>
<td>TEXT</td>
<td></td>
</tr>
<tr>
<td><code>size_bytes</code></td>
<td>BIGINT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>cloud_file_shares</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>file_id</code></td>
<td>UUID</td>
<td>FK → user_files (CASCADE)</td>
</tr>
<tr>
<td><code>token</code></td>
<td>VARCHAR(64)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>permissions</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'read'</td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>user_storage_quotas</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>max_bytes</code></td>
<td>BIGINT</td>
<td>DEFAULT 5368709120</td>
<td>~5 Go</td>
</tr>
<tr>
<td><code>used_bytes</code></td>
<td>BIGINT</td>
<td>DEFAULT 0</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>G. Équipement (Gear)</h2>
<h3><code>gear_items</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users</td>
<td></td>
</tr>
<tr>
<td><code>name</code></td>
<td>VARCHAR(200)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>category</code></td>
<td>VARCHAR(100)</td>
<td></td>
<td>micro, interface, casque...</td>
</tr>
<tr>
<td><code>brand</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>model</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>serial_number</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>image</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>images</code></td>
<td>JSONB</td>
<td>DEFAULT '[]'</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(50)</td>
<td></td>
<td>actif, en réparation, vendu</td>
</tr>
<tr>
<td><code>condition</code></td>
<td>VARCHAR(50)</td>
<td></td>
<td>neuf, bon, usé</td>
</tr>
<tr>
<td><code>purchase_date</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>purchase_price</code></td>
<td>DECIMAL(12,2)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>currency</code></td>
<td>VARCHAR(3)</td>
<td>DEFAULT 'USD'</td>
<td></td>
</tr>
<tr>
<td><code>vendor</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>order_number</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>warranty_start</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>warranty_expire</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>warranty_type</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>warranty_notes</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>support_contact</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>specs</code></td>
<td>JSONB</td>
<td></td>
<td>Spécifications techniques</td>
</tr>
<tr>
<td><code>notes</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>documents</code></td>
<td>JSONB</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>maintenance_history</code></td>
<td>JSONB</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_public</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td>Visible sur le profil</td>
</tr>
<tr>
<td><code>deleted_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td>Soft delete</td>
</tr>
</tbody>
</table>
<h3><code>gear_images</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>gear_id</code></td>
<td>UUID</td>
<td>FK → gear_items (CASCADE)</td>
</tr>
<tr>
<td><code>image_url</code></td>
<td>VARCHAR(500)</td>
<td></td>
</tr>
<tr>
<td><code>position</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
</tr>
</tbody>
</table>
<h3><code>gear_repairs</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>gear_id</code></td>
<td>UUID</td>
<td>FK → gear_items (CASCADE)</td>
</tr>
<tr>
<td><code>repair_date</code></td>
<td>DATE</td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>TEXT</td>
<td></td>
</tr>
<tr>
<td><code>cost_cents</code></td>
<td>INT</td>
<td>DEFAULT 0</td>
</tr>
<tr>
<td><code>currency</code></td>
<td>VARCHAR(3)</td>
<td>DEFAULT 'EUR'</td>
</tr>
<tr>
<td><code>provider</code></td>
<td>VARCHAR(255)</td>
<td></td>
</tr>
<tr>
<td><code>notes</code></td>
<td>TEXT</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>gear_documents</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>gear_id</code></td>
<td>UUID</td>
<td>FK → gear_items (CASCADE)</td>
</tr>
<tr>
<td><code>type</code></td>
<td>VARCHAR(50)</td>
<td>DEFAULT 'invoice'</td>
</tr>
<tr>
<td><code>storage_key</code></td>
<td>TEXT</td>
<td></td>
</tr>
<tr>
<td><code>filename</code></td>
<td>VARCHAR(255)</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>H. Paiements &amp; Royalties</h2>
<h3><code>seller_stripe_accounts</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>stripe_account_id</code></td>
<td>VARCHAR(255)</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>charges_enabled</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>payouts_enabled</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>onboarding_completed</code></td>
<td>BOOL</td>
<td></td>
</tr>
<tr>
<td><code>kyc_status</code></td>
<td>VARCHAR(32)</td>
<td>DEFAULT 'not_started'</td>
</tr>
<tr>
<td><code>kyc_verification_session_id</code></td>
<td>VARCHAR(255)</td>
<td></td>
</tr>
<tr>
<td><code>kyc_verified_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>kyc_last_error</code></td>
<td>TEXT</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>royalty_records</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>content_id</code></td>
<td>UUID</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>INDEX</td>
<td></td>
</tr>
<tr>
<td><code>period</code></td>
<td>VARCHAR</td>
<td>INDEX</td>
<td>Période (ex: 2026-03)</td>
</tr>
<tr>
<td><code>plays</code></td>
<td>BIGINT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>revenue</code></td>
<td>FLOAT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>royalty_amount</code></td>
<td>FLOAT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>royalty_rate</code></td>
<td>FLOAT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'calculated'</td>
<td>calculated/paid</td>
</tr>
<tr>
<td><code>calculated_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>paid_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>royalty_payouts</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>payout_id</code></td>
<td>VARCHAR</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>amount</code></td>
<td>FLOAT</td>
<td></td>
</tr>
<tr>
<td><code>currency</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'EUR'</td>
</tr>
<tr>
<td><code>period</code></td>
<td>VARCHAR</td>
<td>INDEX</td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'pending'</td>
</tr>
<tr>
<td><code>payment_method</code></td>
<td>VARCHAR</td>
<td></td>
</tr>
<tr>
<td><code>transaction_id</code></td>
<td>VARCHAR</td>
<td></td>
</tr>
<tr>
<td><code>processed_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>estimated_arrival</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>notes</code></td>
<td>VARCHAR</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>royalty_rates</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>content_type</code></td>
<td>VARCHAR</td>
<td>UNIQUE</td>
</tr>
<tr>
<td><code>rate</code></td>
<td>FLOAT</td>
<td></td>
</tr>
<tr>
<td><code>description</code></td>
<td>VARCHAR</td>
<td></td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
</tr>
</tbody>
</table>
<h3><code>creator_royalty_rates</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>creator_id</code></td>
<td>UUID</td>
<td>UNIQUE</td>
<td></td>
</tr>
<tr>
<td><code>rate</code></td>
<td>FLOAT</td>
<td></td>
<td>Taux personnalisé</td>
</tr>
<tr>
<td><code>reason</code></td>
<td>VARCHAR</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
</tbody>
</table>
<h3><code>royalty_config</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>platform_fee_rate</code></td>
<td>FLOAT</td>
<td>DEFAULT 0.15</td>
<td>15% frais plateforme</td>
</tr>
<tr>
<td><code>minimum_payout_amount</code></td>
<td>FLOAT</td>
<td>DEFAULT 50.0</td>
<td>Seuil minimum versement</td>
</tr>
<tr>
<td><code>payout_schedule</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'monthly'</td>
<td></td>
</tr>
<tr>
<td><code>processing_delay</code></td>
<td>INT</td>
<td>DEFAULT 3</td>
<td>Jours</td>
</tr>
<tr>
<td><code>currency</code></td>
<td>VARCHAR</td>
<td>DEFAULT 'EUR'</td>
<td></td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>I. Modération &amp; Administration</h2>
<h3><code>reports</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>reporter_id</code></td>
<td>UUID</td>
<td></td>
</tr>
<tr>
<td><code>reported_user_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>content_type</code></td>
<td>VARCHAR(50)</td>
<td></td>
</tr>
<tr>
<td><code>content_id</code></td>
<td>UUID</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>reason</code></td>
<td>TEXT</td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'pending'</td>
</tr>
<tr>
<td><code>resolved_by</code></td>
<td>UUID</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>resolved_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
</tbody>
</table>
<h3><code>announcements</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
</tr>
<tr>
<td><code>title</code></td>
<td>VARCHAR(200)</td>
<td></td>
</tr>
<tr>
<td><code>content</code></td>
<td>TEXT</td>
<td></td>
</tr>
<tr>
<td><code>type</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'info'</td>
</tr>
<tr>
<td><code>is_active</code></td>
<td>BOOL</td>
<td>DEFAULT true</td>
</tr>
<tr>
<td><code>starts_at</code></td>
<td>TIMESTAMP</td>
<td></td>
</tr>
<tr>
<td><code>ends_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
</tr>
<tr>
<td><code>created_by</code></td>
<td>UUID</td>
<td>NULLABLE</td>
</tr>
</tbody>
</table>
<h3><code>notifications</code></h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td>FK → users (CASCADE), INDEX</td>
<td></td>
</tr>
<tr>
<td><code>type</code></td>
<td>VARCHAR(50)</td>
<td>INDEX</td>
<td>follow, like, comment, system...</td>
</tr>
<tr>
<td><code>title</code></td>
<td>VARCHAR(255)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>content</code></td>
<td>TEXT</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>link</code></td>
<td>VARCHAR(500)</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>read</code></td>
<td>BOOL</td>
<td>DEFAULT false</td>
<td>INDEX composite avec user_id</td>
</tr>
<tr>
<td><code>read_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>created_at</code></td>
<td>TIMESTAMP</td>
<td>INDEX</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>J. Exports de données</h2>
<h3><code>data_exports</code> (RGPD)</h3>
<table>
<thead>
<tr>
<th>Colonne</th>
<th>Type</th>
<th>Contraintes</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>id</code></td>
<td>UUID</td>
<td>PK</td>
<td></td>
</tr>
<tr>
<td><code>user_id</code></td>
<td>UUID</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>status</code></td>
<td>VARCHAR(20)</td>
<td>DEFAULT 'pending'</td>
<td>pending/processing/completed/failed</td>
</tr>
<tr>
<td><code>s3_key</code></td>
<td>TEXT</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>file_size_bytes</code></td>
<td>BIGINT</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>expires_at</code></td>
<td>TIMESTAMP</td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>completed_at</code></td>
<td>TIMESTAMP</td>
<td>NULLABLE</td>
<td></td>
</tr>
<tr>
<td><code>error_message</code></td>
<td>TEXT</td>
<td>NULLABLE</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<h2>Utilisation de Redis</h2>
<p>Redis est <strong>optionnel</strong> — dégradation gracieuse si indisponible.</p>
<table>
<thead>
<tr>
<th>Cas d'usage</th>
<th>Pattern de clé</th>
<th>TTL</th>
<th>Fallback</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sessions</strong></td>
<td><code>sessions:{token_hash}</code></td>
<td>Configurable</td>
<td>Table <code>sessions</code></td>
</tr>
<tr>
<td><strong>Cache HTTP</strong></td>
<td><code>http_cache:{sha256(url)}</code></td>
<td>5-15 min</td>
<td>Pas de cache</td>
</tr>
<tr>
<td><strong>Rate limiting</strong></td>
<td><code>ratelimit:{key}</code></td>
<td>Variable</td>
<td>In-memory avec mutex</td>
</tr>
<tr>
<td><strong>Verrouillage compte</strong></td>
<td><code>lockout:{email}:count</code>, <code>lockout:{email}:locked_until</code></td>
<td>Configurable</td>
<td>In-memory map</td>
</tr>
<tr>
<td><strong>Token blacklist</strong></td>
<td><code>token_blacklist:{jti}</code></td>
<td>Durée JWT</td>
<td>Pas de révocation</td>
</tr>
<tr>
<td><strong>Présence utilisateur</strong></td>
<td><code>presence:{user_id}</code></td>
<td>Variable</td>
<td>Table <code>user_presence</code></td>
</tr>
<tr>
<td><strong>Cache applicatif</strong></td>
<td>Via <code>CacheService</code></td>
<td>Variable</td>
<td>Pas de cache</td>
</tr>
</tbody>
</table>
<p>Headers de cache : <code>X-Cache: HIT</code> ou <code>X-Cache: MISS</code></p>
<hr />
<h2>Statistiques</h2>
<table>
<thead>
<tr>
<th>Métrique</th>
<th>Valeur</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fichiers modèles Go</td>
<td>68</td>
</tr>
<tr>
<td>Tables PostgreSQL</td>
<td>60+</td>
</tr>
<tr>
<td>Migrations SQL</td>
<td>115</td>
</tr>
<tr>
<td>Tables avec soft delete</td>
<td>20+</td>
</tr>
<tr>
<td>Tables avec UUIDs</td>
<td>95%+</td>
</tr>
<tr>
<td>Cas d'usage Redis</td>
<td>6 principaux</td>
</tr>
</tbody>
</table>
<hr />
<h2>Documents liés</h2>
<ul>
<li>[[ARCHITECTURE_VEZA]] — Architecture globale</li>
<li>[[ROUTES_API]] — Endpoints API complets</li>
<li>[[SERVEUR_STREAMING_RUST]] — Serveur streaming</li>
<li>[[CONFIGURATION_ENVIRONNEMENT]] — Variables d'environnement</li>
</ul>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Shop/Backend/README.md</div>
</div>
<h1>Backend Talas Shop</h1>
<p>Ce dossier contient le <strong>serveur backend de la boutique Talas</strong> : gestion des produits, du panier, des commandes, des paiements et des retours.</p>
<h2>Objectifs :</h2>
<ul>
<li>Offrir un backend sécurisé, rapide, et évolutif pour la vente de matériel.</li>
<li>Intégrer les paiements (Stripe, ou simulateur).</li>
<li>Gérer les droits, stocks, suivis de commande et retours.</li>
</ul>
<h2>Contenu recommandé :</h2>
<ul>
<li>API REST/GraphQL (<code>/products</code>, <code>/cart</code>, <code>/orders</code>)</li>
<li><code>services/</code> : logique produit, commande, paiement</li>
<li><code>models/</code> : schéma base de données (PostgreSQL recommandé)</li>
<li><code>admin/</code> : endpoints internes (dashboard, statistiques)</li>
<li><code>webhooks/</code> : Stripe, emails transactionnels, retours</li>
</ul>
<blockquote>
<p>Ce backend doit être indépendant de la logique dauth, qui réside dans <code>Auth_&amp;_Core</code>.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Shop/Frontend/README.md</div>
</div>
<h1>Frontend Talas Shop</h1>
<p>Ce dossier contient linterface web de la boutique en ligne Talas : navigation produit, panier, commande, espace client.</p>
<h2>Objectifs :</h2>
<ul>
<li>Créer une expérience utilisateur fluide et accessible.</li>
<li>Mettre en avant les produits, leurs avantages et la réparabilité.</li>
<li>Intégrer le paiement et lespace client.</li>
</ul>
<h2>Contenu recommandé :</h2>
<ul>
<li><code>pages/</code> : accueil, produit, panier, commande, espace client</li>
<li><code>components/</code> : ProductCard, Cart, PaymentForm</li>
<li><code>hooks/</code> : gestion panier, authentification</li>
<li><code>styles/</code> : design system (Tailwind, SCSS, etc.)</li>
<li><code>api/</code> : appels backend (GET produits, POST commande…)</li>
</ul>
<blockquote>
<p>Linterface doit être compatible mobile-first et intégrer des modules d'accessibilité.</p>
</blockquote>
<div class='page-break'></div>
<div class='doc-header'>
<h2>📄 README.md</h2>
<div class='doc-path'>Chemin: 03_APPS_&_SERVICES/Shop/Paiement/README.md</div>
</div>
<h1>Paiement Talas Shop</h1>
<p>Ce dossier isole la logique liée au <strong>paiement</strong> dans la boutique Talas (gestion des transactions, webhooks, factures, remboursements).</p>
<h2>Objectifs :</h2>
<ul>
<li>Sécuriser le processus dachat.</li>
<li>Gérer les erreurs, les remboursements, et les cas limites.</li>
<li>Permettre des extensions futures (paiement fractionné, bons dachat, etc.).</li>
</ul>
<h2>Contenu recommandé :</h2>
<ul>
<li><code>integrations/stripe/</code> : checkout, webhooks</li>
<li><code>simulations/</code> : sandbox de test local</li>
<li><code>pdf/</code> : génération des factures</li>
<li><code>failures/</code> : logs déchecs, cas dusage atypiques</li>
<li><code>README_infra.md</code> : configuration webhook/port</li>
</ul>
<blockquote>
<p>Tu peux isoler ce module en microservice si besoin.</p>
</blockquote>
</body>
</html>