Vue d'ensemble de l'architecture
Présentation générale
Racines est une PWA (Progressive Web App) de compagnon d'apprentissage des langues africaines. L'application est organisée autour de trois rôles (Joueur, Locuteur, Admin) avec des routes et des APIs dédiées.
Diagramme d'architecture
Internet
│
▼
┌─────────────────────────────────────┐
│ Traefik │ ← Reverse proxy + TLS
│ (port 80/443) │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Next.js App │ ← Port 3000
│ (App Router, Server Components) │
│ │
│ ┌──────────┐ ┌────────────────┐ │
│ │ Pages │ │ API Routes │ │
│ │ / │ │ /api/* │ │
│ │ /[lang] │ │ │ │
│ │ /admin │ │ │ │
│ │ /speaker│ │ │ │
│ └──────────┘ └───────┬────────┘ │
└───────────────────────┬┴────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ PostgreSQL │ │ MinIO │
│ (Port 5432) │ │ (Port 9000) │
│ │ │ Bucket: audios │
│ languages │ │ (public-read) │
│ cards │ └─────────────────┘
│ items │ │
│ speakers │ │ URL publique
│ admins │ ▼
│ statistics │ ┌─────────────────┐
│ contact_msgs │ │ CDN / Client │
│ quiz_sessions │ │ (AUDIO_CDN_URL) │
└─────────────────┘ └─────────────────┘
Stack technique
| Couche | Technologie | Rôle |
|---|---|---|
| Framework | Next.js 16 (App Router) | SSR, API Routes, routage |
| UI | React 19 + TypeScript | Composants, état |
| Styling | Tailwind CSS 4 | Styles utilitaires |
| Base de données | PostgreSQL 15 | Données applicatives |
| ORM / Client DB | pg (node-postgres) |
Requêtes SQL brutes avec pool |
| Stockage audio | MinIO (S3-compatible) | Fichiers audio MP3 |
| Traitement audio | FFmpeg (fluent-ffmpeg) | Conversion, compression, normalisation |
| Auth admin | PBKDF2 + JWT (jose) | Authentification sécurisée |
| PWA | Service Worker + IndexedDB v3 | Mode hors ligne |
| Charts | Recharts | Graphiques admin |
| Import Excel | xlsx | Parsing des fichiers .xlsx |
| Nodemailer | Formulaire de contact | |
| Reverse proxy | Traefik | TLS, routing, CORS |
Flux de données principaux
1. Flux de lecture (joueur)
Joueur (navigateur)
│
├─ SSR (Server Component) ──► PostgreSQL → cartes + items
│
├─ Hydratation client
│
└─ Lecture audio ──────────► CDN MinIO (AUDIO_CDN_URL) direct
ou fallback /api/audio/serve/[...path]
2. Flux d'upload audio (locuteur)
Locuteur (navigateur)
│
└─ POST /api/audio/upload
│
├─ Vérification code speaker (table speakers)
├─ FFmpeg processing (mono, 96kbps MP3)
├─ Upload vers MinIO (bucket: audios)
└─ UPDATE items SET audio_url = ... (PostgreSQL)
3. Flux hors ligne (joueur)
Joueur installe l'app
│
└─ Service Worker INSTALL
│
└─ PRECACHE_URLS (assets statiques)
Joueur télécharge une langue
│
└─ Message CACHE_PAGES → Service Worker
│
├─ Étape 1 : Ping /api/health/live
├─ Étape 2 : GET /api/languages/[id]/content → IndexedDB
├─ Étape 3 : Fetch audios → cache racines-audio-cache-v3
├─ Étape 4 : Nettoyage orphelins
└─ Étape 5 : Cache HTML pages
Décisions d'architecture structurantes
Pas de TTS (Text-to-Speech)
L'application n'utilise jamais de voix de synthèse. Si un item n'a pas d'enregistrement natif (audio_url IS NULL), le bouton audio est simplement absent. La qualité prime sur la complétude.
Offline-first
Le contenu est conçu pour fonctionner sans connexion après téléchargement. Le Service Worker est la clé de voûte de cette architecture.
Audio CDN direct
En production, les audios sont servis directement depuis MinIO via AUDIO_CDN_URL — le serveur Next.js n'est pas dans le chemin de lecture. Le proxy fallback (/api/audio/serve/) n'est utilisé qu'en développement.
PostgreSQL avec SQL brut
Pas d'ORM. Les requêtes sont écrites en SQL natif via le pool pg. Cela offre un contrôle total sur les performances et les agrégations complexes.
Service Worker conditionnel
Le SW est désactivé sur /admin et /speaker pour éviter les conflits de cache dans ces interfaces à état.
Structure des dossiers source
src/
├── app/ # Next.js App Router
│ ├── page.tsx # Page d'accueil (/)
│ ├── layout.tsx # Root layout (AUDIO_CDN_URL injection)
│ ├── [language]/ # Pages joueur
│ │ ├── page.tsx # Hub de la langue
│ │ ├── words/ # Défi Mot
│ │ ├── phrases/ # Défi Phrase
│ │ └── revision/ # Révision QCM
│ ├── admin/ # Interface admin (protégée JWT)
│ ├── speaker/ # Interface locuteur
│ ├── contact/ # Formulaire de contact
│ └── api/ # API Routes
├── components/ # Composants React
│ ├── admin/ # Composants admin
│ ├── revision/ # Composants QCM
│ └── [shared]/ # Nav, Audio, Modales...
├── contexts/ # Contextes React
│ ├── LanguageContext.tsx # Déverrouillage langues, flag admin
│ └── SpeakerContext.tsx # Auth locuteur
├── hooks/ # Hooks personnalisés
│ └── useAuth.tsx # Hook auth admin
├── lib/ # Utilitaires et services
│ ├── postgres.ts # Pool PostgreSQL
│ ├── auth.ts # PBKDF2, JWT
│ ├── auth-middleware.ts # Middleware de protection
│ ├── audio-url.ts # Génération URLs audio
│ ├── minio-client.ts # Client MinIO
│ ├── offline-download.ts # Orchestration téléchargement offline
│ ├── offline-data.ts # Lecture données depuis IndexedDB
│ ├── network-state.ts # Circuit breaker réseau
│ ├── statistics.ts # Tracking événements
│ └── quizUtils.ts # Logique QCM
├── types/ # Types TypeScript
│ └── index.ts # Tous les types métier
└── data/
└── quiz-distractors.ts # Données des distracteurs QCM