API REST complète pour l'application ChocoPlus, développée avec NestJS et TypeScript. Cette API gère l'authentification, la gestion des médias (films et séries), le streaming vidéo, les licences, les sélections, et bien plus encore.
- Vue d'ensemble
- Architecture
- Base de données
- Authentification et sécurité
- Internationalisation (i18n)
- API Endpoints
- Intégrations externes
- Configuration
- Installation
- Structure du projet
- Technologies utilisées
L'API ChocoPlus est une API REST robuste qui sert de backend pour l'application desktop ChocoPlus. Elle offre :
- Authentification JWT avec système de vérification par email
- Gestion complète des médias (films, séries, saisons, épisodes)
- Système de licences et sélections pour organiser le contenu
- Streaming vidéo sécurisé avec authentification par token
- Lecture et intégration automatique des fichiers vidéos pour la gestion de bibliothèque multimédia
- Intégration TMDB pour les métadonnées
- Système de support avec envoi d'emails
- Gestion des utilisateurs avec rôles et permissions
- Graphiques et statistiques pour les dashboards
- Internationalisation avec réponses multilingues via
nestjs-i18n
L'API suit l'architecture modulaire de NestJS avec une structure en couches :
┌──────────────────────────────────────────────┐
│ Controllers Layer │
│ (Routes HTTP, Validation, Guards) │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ Services Layer │
│ (Business Logic, Data Processing) │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ Database Layer (MariaDB Pool) │
│ Direct SQL queries via mariadb driver │
│ MariaDB Database │
└──────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────┐
│ External Services │
│ - Node File System (médiathèque) │
│ - TMDB API │
│ - Mail Service (SMTP) |
| - Child Process (execPromise FFmpeg) │
└──────────────────────────────────────────────┘
Chaque module suit le pattern NestJS :
- Module : Configuration et injection de dépendances
- Controller : Gestion des routes HTTP et validation
- Service : Logique métier et accès aux données
- DTO (Data Transfer Objects) : Interfaces pour le typage TypeScript
- Guards : Protection des routes (authentification, rôles)
L'API utilise le driver natif MariaDB (mariadb npm package) avec un pool de connexions pour des performances optimales :
- Module global
DatabaseModulequi crée un pool de connexions réutilisable - Injection directe du pool dans les services via
@Inject(DATABASE_POOL) - Requêtes SQL natives pour un contrôle total et des performances maximales
- Gestion automatique des connexions avec pool
- Configuration via variables d'environnement
Exemple d'utilisation dans un service :
import { Injectable, Inject } from '@nestjs/common';
import { DATABASE_POOL } from '../database/database.module';
import { Pool } from 'mariadb';
@Injectable()
export class MovieService {
constructor(@Inject(DATABASE_POOL) private readonly pool: Pool) {}
async getMovieById(id: number): Promise<any> {
const conn = await this.pool.getConnection();
try {
const rows = await conn.query(
'SELECT * FROM Media WHERE id = ? AND mediaType = "MOVIE"',
[id]
);
return rows[0];
} finally {
conn.release();
}
}
}Avantages de cette approche :
- ✅ Performances optimales (pas de couche d'abstraction ORM)
- ✅ Contrôle total sur les requêtes SQL
- ✅ Flexibilité pour les requêtes complexes avec jointures
- ✅ Pool de connexions pour gérer la concurrence
- ✅ Typage TypeScript avec les interfaces DTO
La base de données MariaDB est le cœur du système ChocoPlus. Elle est organisée en 6 domaines fonctionnels qui reflètent les grandes entités métier de l'application.
┌────────────────────────────────────────────────────────────────────────────┐
│ DOMAINE UTILISATEURS │
│ Profil_Photo ←──── User ────→ User_Media_List │
│ │ │ │
│ └──────────────────┼──→ Stat_User │
└────────────────────────────────────────────┼───────────────────────────────┘
│
┌────────────────────────────────────────────┼───────────────────────────────┐
│ DOMAINE MÉDIAS │ │
│ ↓ │
│ Poster ←──── Media (MOVIE | SERIES) ────→ Translation_Title │
│ ↑ │ │ │
│ │ ├──→ Season ──→ Episode │ │
│ │ ├──→ Media_Credit │ │
│ │ ├──→ Media_Category │ │
│ │ ├──→ Media_Poster │ │
│ │ ├──→ Keyword │ │
│ │ └──→ Similar_Title │ │
└────┼───────────────────────────────────────────────────────────────────────┘
│
┌────┼──────────────────────────────────────────────────────────────────────┐
│ │ DOMAINE ORGANISATION │
│ ↓ │
│ License ────→ License_Media ────→ Media │
│ └────────→ License_Selection ──→ Selection ──→ Selection_Media │
│ └──────────→ Selection_Page │
└───────────────────────────────────────────────────────────────────────────┘
│
┌────┼──────────────────────────────────────────────────────────────────────┐
│ │ DOMAINE ACTUALITÉS │
│ ↓ │
│ News ────────────────────────────→ Media │
│ News_Video_Running ──────────────→ Media │
└───────────────────────────────────────────────────────────────────────────┘
Stocke les avatars disponibles dans l'application.
| Colonne | Type | Description |
|---|---|---|
id |
INT AUTO_INCREMENT | Identifiant unique |
name |
VARCHAR(255) UNIQUE | Nom du fichier image |
isDefaultPhoto |
BOOLEAN | true = photo fournie par défaut |
createdAt / updatedAt |
DATETIME(3) | Horodatages automatiques |
Table centrale des comptes utilisateurs. L'AUTO_INCREMENT commence à 70000 pour que les IDs soient facilement reconnaissables.
| Colonne | Type | Description |
|---|---|---|
id |
INT (début 70000) | Identifiant unique |
pseudo |
VARCHAR(255) UNIQUE | Pseudonyme public |
email |
VARCHAR(255) UNIQUE | Email de connexion |
password |
VARCHAR(255) | Mot de passe hashé (bcrypt, 15 rounds) |
firstName / lastName |
VARCHAR(255) | Nom et prénom |
dateBorn |
DATE | Date de naissance |
role |
ENUM | ADMIN, USER, FAMILY, NOT_ACTIVATE, SUSPENDED |
profilPhoto |
INT (FK) | Référence vers Profil_Photo (nullable) |
Contraintes : email et pseudo sont uniques. La suppression d'une photo de profil met profilPhoto à NULL (ON DELETE SET NULL).
Liste personnelle de favoris de chaque utilisateur (table de jointure Many-to-Many entre User et Media).
| Colonne | Type | Description |
|---|---|---|
userId |
INT (FK) | Référence vers User |
mediaId |
INT (FK) | Référence vers Media |
Contrainte : La combinaison (userId, mediaId) est unique — un même média ne peut être ajouté qu'une fois dans la liste d'un utilisateur.
Suivi de la progression de visionnage de chaque utilisateur.
| Colonne | Type | Description |
|---|---|---|
movieId |
INT (FK, nullable) | Film visionné (ou NULL si épisode) |
episodeId |
INT (FK, nullable) | Épisode visionné (ou NULL si film) |
userId |
INT (FK) | Utilisateur concerné |
state |
ENUM | IN_PROGRESS ou FINISHED |
watchProgress |
FLOAT(5,2) | Pourcentage de progression (ex: 73.45) |
Un enregistrement référence soit un film, soit un épisode — jamais les deux à la fois.
Table centrale qui représente un film (MOVIE) ou une série (SERIES). L'AUTO_INCREMENT commence à 2 000 000 pour distinguer les IDs médias de ceux des autres entités.
| Colonne | Type | Description |
|---|---|---|
id |
INT (début 2 000 000) | Identifiant unique |
title |
VARCHAR(255) UNIQUE | Titre principal (unique) |
mediaLibraryId |
VARCHAR(255) UNIQUE | ID de la médiathèque pour la synchronisation des meta-données du fichier vidéo |
description |
VARCHAR(1024) | Synopsis (nullable) |
date |
DATE | Date de sortie |
time |
BIGINT UNSIGNED | Durée en millisecondes (nullable) |
quality |
VARCHAR(50) | Ex: 4K, 1080p, 720p |
startShow / endShow |
VARCHAR(10) | Période de diffusion (ex: 2020, 2024) |
mediaType |
ENUM | MOVIE ou SERIES |
path |
VARCHAR(555) | Chemin vers le fichier vidéo (films uniquement) |
srcLogo |
INT (FK) | Poster utilisé comme logo |
srcBackground |
INT (FK) | Poster utilisé comme fond d'écran |
Répertoire de toutes les images (logos, fonds, affiches). L'AUTO_INCREMENT commence à 100 000. Le fichier image réel est servi via le module /poster.
| Colonne | Type | Description |
|---|---|---|
id |
INT (début 100 000) | Identifiant unique |
name |
VARCHAR(255) | Nom du fichier image |
Associe plusieurs posters à un média avec un type d'affichage.
| Colonne | Type | Description |
|---|---|---|
mediaId |
INT (FK) | Référence vers Media |
posterId |
INT (FK) | Référence vers Poster |
type |
ENUM | NORMAL, SPECIAL, LICENSE, HORIZONTAL |
Titres traduits d'un média dans différentes langues.
| Colonne | Type | Description |
|---|---|---|
title |
VARCHAR(255) | Titre traduit |
iso_639_1 |
ENUM | Langue : VO, FR, US, ES, DE, IT, JP, RU, KR, CN, PT |
mediaId |
INT (FK) | Référence vers Media |
Genres cinématographiques (Comédie, Action, Horreur…). L'AUTO_INCREMENT commence à 600. 16 catégories par défaut sont insérées au démarrage.
| Colonne | Type | Description |
|---|---|---|
name |
VARCHAR(255) | Nom du genre (ex: Action) |
nameSelection |
VARCHAR(255) | Nom affiché dans les sélections |
Table de jointure Many-to-Many entre Media et Category.
Contrainte : La combinaison (mediaId, categoryId) est unique.
Acteurs et réalisateurs liés aux médias.
| Colonne | Type | Description |
|---|---|---|
fullName |
VARCHAR(255) | Nom complet |
job |
ENUM | ACTOR ou DIRECTOR |
Table de jointure Many-to-Many entre Media et Credit.
Contrainte : La combinaison (mediaId, creditId) est unique.
Mots-clés associés à un média pour améliorer la recherche.
| Colonne | Type | Description |
|---|---|---|
name |
VARCHAR(100) | Le mot-clé |
mediaId |
INT (FK) | Référence vers Media |
Liens de similarité entre médias, utilisés pour les recommandations "Vous aimerez aussi".
| Colonne | Type | Description |
|---|---|---|
sourceId |
INT (FK) | Média source |
targetId |
INT (FK) | Média suggéré |
rate |
DECIMAL(6,3) | Score de similarité |
Contrainte : La paire (sourceId, targetId) est unique.
Les séries (Media avec mediaType = 'SERIES') ont une hiérarchie à trois niveaux : Série → Saison → Épisode.
Saisons d'une série. L'AUTO_INCREMENT commence à 5 000 000.
| Colonne | Type | Description |
|---|---|---|
id |
INT (début 5 000 000) | Identifiant unique |
seriesId |
INT (FK) | Référence vers Media (la série parente) |
mediaLibraryId |
CHAR(36) UNIQUE | ID mediaLibraryId |
name |
VARCHAR(255) | Nom de la saison |
seasonNumber |
INT | Numéro de saison |
srcPoster |
INT (FK) | Poster de la saison (nullable) |
Épisodes d'une série. L'AUTO_INCREMENT commence à 8 000 000.
| Colonne | Type | Description |
|---|---|---|
id |
INT (début 8 000 000) | Identifiant unique |
seriesId |
INT (FK) | Référence vers Media (la série parente) |
seasonId |
INT (FK) | Référence vers Season |
mediaLibraryId |
CHAR(36) UNIQUE | ID mediaLibraryId |
name |
VARCHAR(255) | Titre de l'épisode |
episodeNumber |
INT | Numéro dans la saison |
description |
VARCHAR(1024) | Synopsis |
date |
DATE | Date de diffusion |
time |
BIGINT UNSIGNED | Durée en millisecondes |
quality |
VARCHAR(50) | Qualité vidéo |
path |
VARCHAR(555) | Chemin vers le fichier vidéo |
srcPoster |
INT (FK) | Vignette de l'épisode (nullable) |
Les IDs de début très élevés (
2M,5M,8M) permettent d'identifier immédiatement en base à quelle entité appartient un ID donné.
Ce domaine permet d'organiser le contenu pour l'affichage dans l'application.
Une licence regroupe des médias autour d'une marque ou franchise (ex: Marvel, Star Wars, Disney). L'AUTO_INCREMENT commence à 10 000.
| Colonne | Type | Description |
|---|---|---|
name |
VARCHAR(255) | Nom de la licence |
orderIndex |
INT | Ordre d'affichage (plus petit = affiché en premier) |
position |
BOOLEAN | true = affiché sur la page d'accueil |
srcIcon |
INT (FK) | Icône de la licence |
srcLogo |
INT (FK) | Logo de la licence |
srcBackground |
INT (FK) | Image de fond |
Médias appartenant à une licence, avec un ordre d'affichage.
| Colonne | Type | Description |
|---|---|---|
licenseId |
INT (FK) | Référence vers License |
mediaId |
INT (FK) | Référence vers Media |
orderIndex |
INT | Ordre dans la licence |
Une sélection est une collection thématique de médias (ex: "Les meilleurs films d'animation"). L'AUTO_INCREMENT commence à 30 000.
| Colonne | Type | Description |
|---|---|---|
name |
VARCHAR(255) | Nom de la sélection |
selectionType |
ENUM | NORMAL_POSTER ou SPECIAL_POSTER (type d'affichage) |
Médias dans une sélection, triés par orderIndex.
Contrainte : La combinaison (selectionId, mediaId) est unique.
Détermine sur quelles pages une sélection est affichée et dans quel ordre.
| Colonne | Type | Description |
|---|---|---|
selectionId |
INT (FK) | Référence vers Selection |
orderIndex |
INT | Ordre d'affichage sur la page |
pageType |
ENUM | HOME, MOVIES ou SERIES |
Lie une sélection à une licence, avec un ordre d'affichage dans la page de la licence.
Contrainte : La combinaison (licenseId, selectionId) est unique.
Actualités affichées sur la page d'accueil, associées à un média. L'AUTO_INCREMENT commence à 90 000.
| Colonne | Type | Description |
|---|---|---|
srcBackground |
INT (FK) | Image de fond (nullable) |
orientation |
INT | Disposition visuelle (1, 2 ou 3) |
mediaId |
INT (FK) | Média associé à l'actualité |
orderIndex |
INT | Ordre d'affichage |
Vidéos promotionnelles défilantes sur les pages Films et Séries. L'AUTO_INCREMENT commence à 50 000.
| Colonne | Type | Description |
|---|---|---|
srcBackground |
INT (FK) | Image de fond (nullable) |
startShow / endShow |
VARCHAR(10) | Période d'affichage |
mediaLibraryId |
CHAR(36) | ID mediaLibraryId de la vidéo |
activated |
BOOLEAN | activer la news pour la diffuser sur la page principal |
path |
VARCHAR(555) | Chemin vers le fichier vidéo |
mediaId |
INT (FK) | Média lié |
Toutes les relations respectent une politique cohérente :
ON DELETE RESTRICT: Empêche la suppression d'un enregistrement parent s'il est encore référencé. Par exemple, on ne peut pas supprimer unMedias'il existe encore desEpisode,Keywordou entrées dansUser_Media_Listqui le référencent.ON DELETE SET NULL: Utilisé pour les champs optionnels commeprofilPhoto(User),srcLogoousrcBackground(Media). La suppression d'un poster met simplement la référence à NULL sans bloquer.ON UPDATE CASCADE: Toutes les relations propagent automatiquement les modifications d'ID parent.
Tous les champs de jointure (userId, mediaId, selectionId, etc.) sont indexés pour garantir des performances optimales sur les requêtes avec jointures.
Lors de l'importation de db.sql, les données suivantes sont insérées automatiquement :
Compte administrateur : Un utilisateur ChocoPops avec le rôle ADMIN et le mot de passe 'Password1234' est créé.
Function LEVENSHTEIN : Obtenir la distance entre deux chaînes de caractères pour les requêtes de recherche.
Function NORMALIZE_TITLE : Formaté une chaîne de caractère (suppression des accents, des caractères spéciaux, des majuscules ...)
L'API utilise un système d'authentification robuste basé sur JWT (JSON Web Tokens) :
-
Inscription avec vérification email :
- L'utilisateur s'inscrit avec ses informations
- Un code de vérification à 6 chiffres est envoyé par email
- L'utilisateur valide son email avec ce code
- Le compte est créé avec le rôle
NOT_ACTIVATE - Un admin doit activer le compte pour que l'utilisateur puisse se connecter
-
Connexion :
- Login avec email et mot de passe (hashé avec bcrypt)
- Retourne un access token JWT
- Le token contient l'ID utilisateur (
sub) et le rôle
-
Protection des routes :
- Routes publiques : Décorateur
@Public() - Routes authentifiées : Guard JWT par défaut
- Routes admin :
@UseGuards(AdminUserGuard) - Extraction de l'utilisateur courant :
@CurrentUser('sub')
- Routes publiques : Décorateur
- JwtAuthGuard : Vérifie la validité du JWT (appliqué globalement)
- AdminUserGuard : Vérifie que l'utilisateur a le rôle
ADMIN - Public Decorator : Exempte une route de l'authentification JWT
enum Role {
ADMIN // Accès complet à toutes les fonctionnalités
USER // Utilisateur standard activé
FAMILY // Membre de la famille (permissions limitées)
NOT_ACTIVATE // Compte créé mais non activé par un admin
SUSPENDED // Compte suspendu
}L'API supporte plusieurs langues pour ses messages de réponse grâce à nestjs-i18n. La langue est déterminée automatiquement à partir du header de chaque requête.
| Drapeau | Langue | Code |
|---|---|---|
| 🇫🇷 | Français | fr |
| 🇬🇧 | English | en |
| 🇯🇵 | 日本語 | ja |
Si le header est égale à none, alors la clé de traduction est envoyé. Si aucun header valide n'est fourni, la langue par défaut est l'anglais (
en).
La langue est lue depuis le header HTTP lang (ou accept-language en fallback) via un resolver personnalisé :
// Envoyer le header dans chaque requête
lang: 'fr' // Français
lang: 'en' // Anglais
lang: 'ja' // Japonais@Injectable()
export class HeaderLanguageResolver implements I18nResolver {
resolve(context: ExecutionContext): string {
const request = context.switchToHttp().getRequest();
const lang = request.headers['lang'] ?? request.headers['accept-language'];
if (!lang || !AVAILABLE_LANGS.includes(lang)) {
return DEFAULT_LANG; // 'en' par défaut
}
return lang;
}
}Les fichiers de traduction sont organisés par langue dans src/i18n/ :
src/
└── i18n/
├── fr/
│ └── common.json # Messages en français
├── en/
│ └── common.json # Messages en anglais
└── ja/
└── common.json # Messages en japonais
Chaque fichier common.json contient les clés de traduction organisées par domaine fonctionnel (AUTH, USER, MEDIA, MOVIE, SERIES, LIBRARY, CREDIT, etc.).
Les messages traduits sont injectés via le service I18nService de nestjs-i18n :
import { I18nService } from 'nestjs-i18n';
@Injectable()
export class MovieService {
constructor(private readonly i18n: I18nService) {}
async insertNewMovie(...): Promise<ReturnMessage> {
// ...
return { message: this.i18n.t('common.MOVIE.MOVIE_REGISTERED', { args: { title } }) };
}
}Légende : ✅ = requis / ✅ Admin = rôle
ADMINrequis en plus du JWT
| Méthode | Route | Description | Public |
|---|---|---|---|
| POST | /auth/login |
Connexion utilisateur | ✅ |
| POST | /auth/send-verification-code |
Envoi du code de vérification par email | ✅ |
| POST | /auth/register |
Validation du code et création du compte | ✅ |
| POST | /auth/resend-verification-code |
Renvoyer un code de vérification | ✅ |
Flux d'inscription complet :
1. POST /auth/send-verification-code → Envoie code à l'email
2. POST /auth/register (avec code) → Crée le compte (NOT_ACTIVATE)
3. Admin change le rôle → Envoi email avec mot de passe
4. POST /auth/login → Connexion possible
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /user/current-user |
Informations de l'utilisateur connecté | ✅ | - |
| GET | /user/all-user |
Liste de tous les utilisateurs | ✅ | ✅ |
| GET | /user/my-list |
Ma liste personnelle de médias | ✅ | - |
| PUT | /user/toggle-into-my-list/:mediaId |
Ajouter/retirer un média de ma liste | ✅ | - |
| PUT | /user/profil-picture/:idProfilPicture |
Changer la photo de profil | ✅ | - |
| PUT | /user/update-user-by-user |
Modifier ses informations personnelles | ✅ | - |
| PUT | /user/update-role-by-admin/:id |
Changer le rôle d'un utilisateur | ✅ | ✅ |
| DELETE | /user |
Supprimer son propre compte | ✅ | - |
| DELETE | /user/delete-user-by-admin/:id |
Supprimer un utilisateur | ✅ | ✅ |
Note : Lorsqu'un admin change le rôle d'un utilisateur de NOT_ACTIVATE à USER ou FAMILY, un email est automatiquement envoyé avec un nouveau mot de passe.
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /movie/nodes |
Liste simplifiée (nodes) de tous les films | ✅ | - |
| GET | /movie/research/:keyWord |
Recherche de films par mot-clé | ✅ | - |
| GET | /movie/random-movie |
Film aléatoire | ✅ | - |
| GET | /movie/watchProgress/:movieId |
Progression de visionnage d'un film | ✅ | - |
| GET | /movie/:id |
Détails complets d'un film | ✅ | - |
| POST | /movie/add |
Ajouter un nouveau film | ✅ | ✅ |
| PUT | /movie/modify |
Modifier un film existant | ✅ | ✅ |
| DELETE | /movie/delete/:id |
Supprimer un film | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /series/nodes |
Liste simplifiée (nodes) de toutes les séries | ✅ | - |
| GET | /series/research/:keyWord |
Recherche de séries par mot-clé | ✅ | - |
| GET | /series/episodes/:idSeries/:idSeason |
Épisodes d'une saison pour l'utilisateur courant | ✅ | - |
| GET | /series/random-series |
Série aléatoire | ✅ | - |
| GET | /series/first-episode/:seriesId |
Premier épisode d'une série | ✅ | - |
| GET | /series/last-episode-watched/:seriesId |
Dernier épisode visionné par l'utilisateur | ✅ | - |
| GET | /series/watchProgress/:episodeId |
Progression de visionnage d'un épisode | ✅ | - |
| GET | /series/:id |
Détails complets d'une série | ✅ | - |
| POST | /series/add |
Ajouter une nouvelle série | ✅ | ✅ |
| PUT | /series/modify |
Modifier une série existante | ✅ | ✅ |
| DELETE | /series/delete/:id |
Supprimer une série | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /media/research/:keyword |
Recherche globale (films + séries) | ✅ | - |
| GET | /media/media-info/:mediaId |
Informations complètes d'un média | ✅ | - |
| GET | /media/null-poster |
Médias sans poster (maintenance) | ✅ | ✅ |
| POST | /media/catalog |
Catalogue filtré et trié (body: filtres, query: sort/pagination) | ✅ | - |
Paramètres de /media/catalog :
Query params : sortFilter, orderDirection, count, offset
Body : tableau de filtres FILTERS[]
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /credit/job-filters |
Liste des métiers disponibles pour le filtrage | ✅ | - |
| GET | /credit/research/:keyword |
Recherche de crédits (acteurs, réalisateurs…) | ✅ | - |
| GET | /credit/:creditId |
Détails d'un crédit par son ID | ✅ | - |
| POST | /credit/save-all-new-credit |
Rafraîchir les crédits de tous les médias | ✅ | ✅ |
| POST | /credit/add |
Ajouter un nouveau crédit | ✅ | ✅ |
| PUT | /credit/modify |
Modifier un crédit existant | ✅ | ✅ |
| DELETE | /credit/delete/:creditId |
Supprimer un crédit | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /category/graph |
Données pour graphique des catégories | ✅ | - |
| GET | /category/all-categories |
Liste de toutes les catégories | ✅ | - |
| GET | /category/:id |
Détails d'une catégorie avec ses médias | ✅ | - |
| POST | /category/save-category |
Créer une nouvelle catégorie | ✅ | ✅ |
| PUT | /category/update-category |
Modifier une catégorie | ✅ | ✅ |
| DELETE | /category/:id |
Supprimer une catégorie | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /license/graph |
Données pour graphique des licences | ✅ | - |
| GET | /license/home-page |
Licences affichées sur la page d'accueil | ✅ | - |
| GET | /license/research-page |
Licences pour la page de recherche | ✅ | - |
| GET | /license/research/:keyWord |
Recherche de licences | ✅ | - |
| GET | /license/:id |
Détails complets d'une licence | ✅ | - |
| POST | /license/add |
Créer une nouvelle licence | ✅ | ✅ |
| PUT | /license/modify |
Modifier une licence | ✅ | ✅ |
| PUT | /license/change-order-home-license |
Réorganiser l'ordre des licences (accueil) | ✅ | ✅ |
| PUT | /license/change-order-research-license |
Réorganiser l'ordre des licences (recherche) | ✅ | ✅ |
| DELETE | /license/delete/:id |
Supprimer une licence | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /selection/graph |
Données pour graphique des sélections | ✅ | - |
| GET | /selection/selection-home |
Sélections pour la page d'accueil | ✅ | - |
| GET | /selection/random-media-selection-by-type/:mediaType |
Sélections aléatoires par type de média | ✅ | - |
| GET | /selection/research/:keyWord |
Recherche de sélections | ✅ | - |
| GET | /selection/:id |
Détails d'une sélection avec ses médias | ✅ | - |
| POST | /selection/add |
Créer une nouvelle sélection | ✅ | ✅ |
| PUT | /selection/modify |
Modifier une sélection | ✅ | ✅ |
| PUT | /selection/update-selection-page-home |
Mettre à jour les sélections de la page d'accueil | ✅ | ✅ |
| DELETE | /selection/delete/:id |
Supprimer une sélection | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /news |
Liste de toutes les actualités | ✅ | - |
| PUT | /news |
Mettre à jour les actualités | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /news-video-running/movies |
Vidéo aléatoire pour la page Films | ✅ | - |
| GET | /news-video-running/series |
Vidéo aléatoire pour la page Séries | ✅ | - |
| GET | /news-video-running/all-movies |
Toutes les vidéos (Films) | ✅ | - |
| GET | /news-video-running/all-series |
Toutes les vidéos (Séries) | ✅ | - |
| PUT | /news-video-running/movies |
Mettre à jour les vidéos (Films) | ✅ | ✅ |
| PUT | /news-video-running/series |
Mettre à jour les vidéos (Séries) | ✅ | ✅ |
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /similar-title/links |
Liens entre médias similaires | ✅ | - |
| GET | /similar-title/:id |
Titres similaires pour un média | ✅ | - |
| GET | /similar-title/movie-with-less-similar-titles |
Médias avec peu de suggestions | ✅ | ✅ |
| PUT | /similar-title/rewrite-all-data |
Recalculer tous les titres similaires | ✅ | ✅ |
Routes de streaming vidéo sécurisées avec authentification par token query.
| Méthode | Route | Description | Public |
|---|---|---|---|
| GET | /stream/stream-movie/:movieId?token=xxx |
Stream d'un film | ✅ |
| GET | /stream/stream-episode/:seasonId/:episodeId?token=xxx |
Stream d'un épisode | ✅ |
| GET | /stream/stream-news/:newsId?token=xxx |
Stream d'une vidéo news | ✅ |
Note : Ces routes sont publiques mais nécessitent un token JWT valide en query parameter.
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /profil-photo |
Liste des photos de profil disponibles | ✅ | - |
| POST | /profil-photo |
Remplir la base avec des photos par défaut | ✅ | ✅ |
Recherche de métadonnées depuis l'API The Movie Database. Supporte la recherche par titre ou par ID TMDB numérique.
| Méthode | Route | Description | Auth |
|---|---|---|---|
| GET | /tmdb/search-movie-tmdb/:movie |
Chercher un film (titre ou ID TMDB) | ✅ |
| GET | /tmdb/search-movie-library/:mediaLibraryId |
Chercher un film via son mediaLibraryId | ✅ |
| GET | /tmdb/search-series-tmdb/:series |
Chercher une série (titre ou ID TMDB) | ✅ |
| GET | /tmdb/search-series-library/:mediaLibraryId |
Chercher une série via son mediaLibraryId | ✅ |
| GET | /tmdb/search-credit-by-id/:id |
Chercher un crédit par ID TMDB | ✅ |
| GET | /tmdb/search-credit-by-full-name/:name |
Chercher un crédit par nom complet | ✅ |
Note : Pour les routes search-movie-tmdb et search-series-tmdb, si le paramètre est numérique il est traité comme un ID TMDB, sinon comme un titre.
Gestion des médiathèques et synchronisation des fichiers vidéo.
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /library/media-missing-files |
Médias avec des chemins de fichiers invalides | ✅ | ✅ |
| GET | /library/orphan-media-library |
Médiathèques orphelines (sans média associé) | ✅ | ✅ |
| GET | /library/duplicate-tmdb |
Médias avec des ID TMDB dupliqués | ✅ | ✅ |
| GET | /library/libraries |
Liste de toutes les médiathèques | ✅ | ✅ |
| GET | /library/media-libraries/:libraryId |
Liste des médias d'une médiathèque | ✅ | ✅ |
| POST | /library |
Créer une nouvelle médiathèque | ✅ | ✅ |
| PUT | /library/refresh/:id/:mediaType |
Synchroniser une médiathèque (scan + import) | ✅ | ✅ |
| PUT | /library/modify-tmbd/:mediaLibraryId |
Modifier l'ID TMDB d'un média de la médiathèque | ✅ | ✅ |
| PUT | /library/modify-path/:mediaLibraryId |
Modifier le chemin du fichier du média de la médiathèque | ✅ | ✅ |
| PUT | /library/reload-media-library-metadata/:mediaLibraryId |
Recharger les métadonnées d'un média | ✅ | ✅ |
| PUT | /library/reload-media-library-file/:mediaLibraryId |
Recharger le fichier vidéo d'un média | ✅ | ✅ |
| DELETE | /library/:id |
Supprimer une médiathèque | ✅ | ✅ |
Gestion des versions de l'application par système d'exploitation.
| Méthode | Route | Description | Auth | Admin |
|---|---|---|---|---|
| GET | /version/windows |
Dernière version pour Windows | ✅ | - |
| GET | /version/linux |
Dernière version pour Linux | ✅ | - |
| GET | /version/macos |
Dernière version pour macOS | ✅ | - |
| GET | /version/all |
Toutes les dernières versions (tous OS) | ✅ | - |
| PUT | /version |
Mettre à jour la version pour un OS | ✅ | ✅ |
| Méthode | Route | Description | Auth |
|---|---|---|---|
| GET | /support |
Liste des formulaires de support | ✅ |
| POST | /support |
Envoyer un formulaire de support par email | ✅ |
Format du formulaire :
{
subject: string; // Sujet du problème
areaConcerned: string; // Page/Zone concernée
description: string; // Description détaillée
}Le service de mail utilise Nodemailer avec des templates Handlebars (.hbs) pour envoyer des emails stylisés.
Templates disponibles (dans /templates) :
verification-code.hbs: Code de vérification à 6 chiffrespassword.hbs: Nouveau mot de passe lors de l'activationsuspended.hbs: Notification de suspension de compte
Emails envoyés automatiquement :
- Inscription : Code de vérification (6 chiffres)
- Activation par admin : Nouveau mot de passe généré
- Suspension : Notification de suspension
- Support : Formulaire de bug transmis aux admins
Configuration SMTP (variables d'environnement) :
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USER=noreply@chocoplus.com
MAIL_PASS=xxxxx
MAIL_FROM="ChocoPlus <noreply@chocoplus.com>"Intégration selon les fichiers vidéos disponibles dans les médiathèques créées :
- Synchronisation automatique des bibliothèques
- Récupération des chemins de fichiers vidéo + des métadonnées avec ffmpeg
- Gestion des métadonnées (posters, descriptions)
- Détection des nouveaux contenus
Récupération des métadonnées depuis TMDB :
- Informations complètes des films/séries
- Posters et images HD
- Cast et équipe technique
- Traductions multilingues
- Titres similaires
Créer un fichier .env à la racine :
# Database (MariaDB)
DB_HOST="localhost"
DB_PORT=3306
DB_USER="root"
DB_PASS="votre-mot-de-passe"
DB_NAME="chocoplus"
DB_CONNECTION_LIMIT=10
API_URL="http://localhost:3000"
# JWT
JWT_SECRET="votre-secret-jwt-super-securise"
JWT_EXPIRES_IN="7d"
# Mail (SMTP)
MAIL_HOST="smtp.gmail.com"
MAIL_PORT=587
MAIL_USER="votre-email@gmail.com"
MAIL_PASS="votre-mot-de-passe-app"
MAIL_FROM="ChocoPlus <noreply@chocoplus.com>"
# TMDB API
TMDB_API_KEY="votre-cle-api-tmdb"
TMDB_BASE_URL="https://api.themoviedb.org/3"
# Application
PORT=3000
NODE_ENV=development
# Header sécurité
HEADER_SECRET_API="votre-cle-secrete-api"
HEADER_NAME_FIELD_SECRET_API="nom-du-header"Le projet utilise le driver natif MariaDB avec un pool de connexions configuré dans database.module.ts.
const pool = mariadb.createPool({
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
user: config.get('DB_USER'),
password: config.get('DB_PASS'),
database: config.get('DB_NAME'),
connectionLimit: config.get('DB_CONNECTION_LIMIT'),
decimalAsNumber: true,
bigIntAsNumber: true
});Recommandations :
DB_CONNECTION_LIMIT: 10-20 pour usage standard, plus pour haute charge- Pool global injecté dans tous les services via
@Inject(DATABASE_POOL) - Toujours libérer les connexions avec
conn.release()dans un blocfinally
- Node.js : 18.x ou supérieur
- npm : 9.x ou supérieur
- MariaDB : 10.x ou supérieur
- Ffmpeg : pour la récupération des métadonnées des fichiers vidéo (durée, résolution…)
- Compte TMDB : pour les métadonnées des films/séries
# 1. Cloner le repository
git clone <repository-url>
cd chocoserv
# 2. Installer les dépendances
npm install
# 3. Configurer les variables d'environnement
cp .env.example .env
# Éditer .env avec vos configurations
# 4. Configurer la base de données
mariadb -u root -p -e "CREATE DATABASE chocoplus CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mariadb -u root -p chocoplus < db.sql
# 5. Lancer en mode développement
npm run start:dev
# 6. L'API est accessible sur http://localhost:3000npm run start # Lancer en mode production
npm run start:dev # Lancer en mode développement (hot-reload)
npm run start:debug # Lancer en mode debug
npm run build # Compiler pour la production
npm run test # Tests unitaires
npm run test:e2e # Tests end-to-end
npm run lint # Vérification du codechocoplus-api/
├── src/
│ ├── auth/ # Module d'authentification
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── auth.module.ts
│ │ └── dto/
│ │
│ ├── user/ # Module utilisateurs
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ ├── user.module.ts
│ │ └── dto/
│ │
│ ├── movie/ # Module films
│ │ ├── movie.controller.ts
│ │ ├── movie.service.ts
│ │ ├── movie.module.ts
│ │ └── dto/
│ │
│ ├── series/ # Module séries
│ │ ├── series.controller.ts
│ │ ├── series.service.ts
│ │ ├── series.module.ts
│ │ └── dto/
│ │
│ ├── media/ # Module médias génériques
│ │ ├── media.controller.ts
│ │ ├── media.service.ts
│ │ └── media.module.ts
│ │
│ ├── credit/ # Module crédits (acteurs, réalisateurs et autres)
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── category/ # Module catégories
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── license/ # Module licences
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── selection/ # Module sélections
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── news/ # Module actualités
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── news-video-running/ # Module vidéos tournantes
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── stream/ # Module streaming
│ │ ├── stream.controller.ts
│ │ ├── stream.service.ts
│ │ └── stream.module.ts
│ │
│ ├── poster/ # Module posters
│ │ └── poster.controller.ts
│ │
│ ├── profil-photo/ # Module photos de profil
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── similar-title/ # Module titres similaires
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── support/ # Module support
│ │ ├── support.controller.ts
│ │ ├── support.service.ts
│ │ └── dto/
│ │
│ ├── tmdb/ # Module TMDB
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── library/ # Module médiathèques
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── version/ # Module versions
│ │ ├── controller/
│ │ ├── service/
│ │ └── dto/
│ │
│ ├── i18n/ # Fichiers de traduction (nestjs-i18n)
│ │ ├── fr/
│ │ │ └── common.json # Messages en français
│ │ ├── en/
│ │ │ └── common.json # Messages en anglais
│ │ └── ja/
│ │ └── common.json # Messages en japonais
│ │
│ ├── guard/ # Guards d'authentification
│ │ ├── jwt-auth.guard.ts
│ │ ├── admin-user.guard.ts
│ │ ├── current-user.guard.ts
│ │ ├── header-language.resolver.ts
│ │ └── public.decorator.ts
│ │
│ ├── common-service/ # Services partagés
│ │ └── mail.service.ts
│ │
│ ├── common-interface/ # Interfaces partagées
│ │ ├── return-message.interface.ts
│ │ ├── graph.interface.ts
│ │ ├── node.interface.ts
│ │ └── link.interface.ts
│ │
│ ├── templates/ # Templates d'emails (Handlebars)
│ │ ├── layout.hbs
│ │ ├── verification-code.hbs
│ │ ├── password.hbs
│ │ └── suspended.hbs
│ │
│ ├── database/ # Module de base de données
│ │ ├── database.module.ts
| | └── sql
| | └── db.sql (shéma entier de la base de donnée avec fonction de recherche)
│ │
│ ├── app.controller.ts
│ ├── app.service.ts
│ ├── app.module.ts
│ └── main.ts
│
├── test/
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
│
├── db.sql
├── .env
├── .env.example
├── nest-cli.json
├── tsconfig.json
├── package.json
└── README.md
| Technologie | Version | Usage |
|---|---|---|
| NestJS | 10.x | Framework backend Node.js |
| TypeScript | 5.x | Langage de programmation |
| Node.js | 18.x+ | Runtime JavaScript |
| Ffmpeg | 8.x+ | Child Process |
| Technologie | Usage |
|---|---|
| MariaDB | Base de données relationnelle |
| mariadb (npm) | Driver natif Node.js avec pool de connexions |
| Technologie | Usage |
|---|---|
| JWT (jsonwebtoken) | Tokens d'authentification |
| bcrypt | Hashage des mots de passe |
| Passport JWT | Stratégie d'authentification |
| Technologie | Usage |
|---|---|
| nestjs-i18n | Internationalisation des messages de réponse |
| Technologie | Usage |
|---|---|
| Nodemailer | Service SMTP |
| Handlebars (HBS) | Templates d'emails |
| Service | Usage |
|---|---|
| TMDB API | Métadonnées films/séries |
| Outil | Usage |
|---|---|
| ESLint | Linting du code |
| Prettier | Formatage du code |
| Jest | Tests unitaires |
| Supertest | Tests E2E |
- HTTPS recommandé pour la production
- CORS configuré pour autoriser uniquement l'application Electron
- Rate limiting pour éviter les abus
- Validation des entrées avec class-validator
- Sanitization des données utilisateur
- Tokens JWT avec expiration
- Mots de passe hashés avec bcrypt (salt rounds: 15)
- Pool de connexions : Réutilisation des connexions pour éviter l'overhead
- Libération systématique : Toujours utiliser
finallypourconn.release() - Requêtes préparées : Protection contre les injections SQL via paramètres
? - Pagination sur les listes de médias
- Indexation des champs de recherche dans la base de données
- Streaming vidéo optimisé avec range requests
async getMovies(limit: number, offset: number): Promise<Movie[]> {
const conn = await this.pool.getConnection();
try {
const rows = await conn.query(
'SELECT * FROM Media WHERE mediaType = ? LIMIT ? OFFSET ?',
['MOVIE', limit, offset]
);
return rows;
} finally {
conn.release();
}
}1. POST /auth/send-verification-code → Envoie code à l'email
2. POST /auth/register (avec code) → Crée le compte (NOT_ACTIVATE)
3. Admin change le rôle → Envoi email avec mot de passe
4. POST /auth/login → Connexion possible
Le streaming utilise Range Requests pour permettre :
- La lecture en continu
- Le seek dans la vidéo
- La gestion de la bande passante
- Le support des sous-titres
L'API peut :
- Détecter automatiquement les nouveaux médias dans les médiathèques créées
- Importer les métadonnées avec ffmpeg
- Synchroniser les chemins de fichiers
- Détecter les incohérences (médias supprimés, IDs invalides, TMDB dupliqués)
Ce projet est public.
Projet public - Aucun droit réservé
Développé avec ❤️ et 🍫 par l'équipe ChocoPlus
API Version 2.0.0 - Mai 2026