Nantes, France
Enzo Peigné — Portfolio · Ingénieur Cybersécurité —
Projet · Web

TVRecap

Application de suivi de films et séries, avec intégration de l'API TMDB pour les données et affiches.

Aucune application de suivi de films et séries existante ne correspondait exactement à l’usage voulu : simple, auto-hébergée, sans abonnement, sans tracking publicitaire, avec un vrai suivi par épisode et non juste par titre. TVRecap est né de ce besoin. Elle avait également une vocation pédagogique : construire une application web complète de zéro, avec authentification, gestion de données, intégration d’API tierce, et emails transactionnels.

C’est le premier projet web de taille significative développé de zéro, déployé sur l’infrastructure personnelle, et maintenu en production. Ce statut de premier projet réel lui donne une place particulière dans le portfolio, même si l’architecture initiale monolithique accuse aujourd’hui ses choix de 2024.

Contexte

Les plateformes de suivi grand public (Letterboxd, Trakt, Simkl) imposent leurs contraintes : données hébergées sur des serveurs tiers, monétisation via publicité ou abonnement, impossibilité de personnaliser l’interface ou les flux de données.

L’objectif était différent : une application auto-hébergée sur l’infrastructure existante, qui consomme l’API TMDB pour les métadonnées et stocke la progression localement, sans dépendance à un service externe pour les données utilisateur.

Objectifs

  • Suivre la progression par épisode pour les séries : épisodes vus un à un, vue saison par saison, compteur global de temps de visionnage calculé à partir des durées TMDB.
  • Suivre les films vus avec une interface simple de marquage “vu/non vu”, sans complexité de notation ou de classement.
  • Intégrer l’API TMDB pour les métadonnées enrichies : affiches, résumés, casting, catégories, réalisateurs.
  • Gérer plusieurs comptes utilisateurs avec authentification sécurisée, vérification par email et réinitialisation de mot de passe.
  • Envoyer des emails transactionnels : confirmation d’inscription, réinitialisation de mot de passe (token à 15 min), demandes de contact.
  • Permettre l’ajout de contenu via une interface d’administration dédiée.

Architecture

L’application est une architecture PHP monolithique classique : rendu serveur, jQuery/Ajax pour les interactions asynchrones sans rechargement de page, MariaDB pour la persistance.

Schéma de données

Cinq tables : Accounts (utilisateurs, tokens de session, tokens de vérification et de reset), Films (métadonnées complètes), Séries (avec nombre de saisons), Episodes (numéro, durée, saison, référence série), Seen_List (liaison user × film ou user × épisode, type movie ou serie).

Le suivi par épisode est granulaire : chaque ligne de Seen_List référence soit un film_id, soit un episode_id. Les contraintes d’unicité (unique_movie_user_entry, unique_episode_user_entry) empêchent les doublons sans logique applicative supplémentaire.

Authentification

La gestion de session est entièrement maison. À la connexion, un token de 16 octets est généré via bin2hex(random_bytes(16)), haché en SHA-256, stocké en base avec sa date d’expiration, et transmis au navigateur via un cookie USERSESSION avec les attributs Secure, HttpOnly et un domaine strict (tvrecap.epeigne.fr). L’option “Se souvenir de moi” ajuste la durée entre 1 jour et 1 mois.

La réinitialisation de mot de passe suit le même principe : token unique haché, expiration à 15 minutes, lien envoyé par email via PHPMailer. Les tokens de vérification de compte à l’inscription fonctionnent identiquement.

Emails transactionnels — PHPMailer + Stalwart

Tous les emails (vérification de compte, reset de mot de passe, demandes de contact) passent par PHPMailer configuré contre le serveur mail Stalwart auto-hébergé sur l’infrastructure existante. L’adresse expéditrice [email protected] est dédiée à l’application. Les emails de contact entrants sont routés vers [email protected] avec l’adresse du visiteur en expéditeur, ce qui permet de répondre directement.

Les templates HTML des emails sont intégrés dans le PHP — une décision pragmatique pour un projet solo, qui sera extraite en fichiers séparés lors de la migration.

Intégration TMDB — scripts Python d’administration

L’interface d’administration (/AdminAddContent) permet d’ajouter des films et séries via trois scripts Python appelés par PHP via shell_exec() :

  • searchMov.py — recherche dans l’API TMDB par titre et année, retourne les métadonnées (casting des 3 premiers acteurs, genres, durée, synopsis) en JSON pour affichage dans l’interface avant confirmation.
  • getEpisodes.py — récupère l’ensemble des épisodes d’une série par TMDB ID (numéro, nom, durée, saison) et les insère directement en base via mysql.connector. Parcourt les saisons en incrémentant jusqu’à retour None.
  • genImg.py — télécharge l’affiche depuis l’URL TMDB et la sauve localement dans img/Covers/movies/ ou img/Covers/series/. Intègre une protection SSRF : validation du schéma (http/https uniquement), des extensions autorisées, résolution DNS et vérification que l’IP résolue n’appartient pas aux plages privées/localhost bloquées.

Les métadonnées sont mises en cache localement en base pour éviter les appels répétés et respecter les limites de rate de l’API TMDB.

Routing Apache

L’application utilise mod_rewrite pour des URLs propres : /movieshtml/movies.html, /serieDetailshtml/serie-details.html, /AdminHomePanelad/html/adhome.html, etc. L’interface d’administration est physiquement séparée dans le dossier ad/ avec ses propres CSS, JS et PHP.

Réalisations

Le catalogue atteint 640 films, 64 séries et 4 946 épisodes, tous insérés via les scripts Python d’administration à partir de données TMDB. Les fichiers SQL (data.sql, series.sql, episodes.sql) représentent le résultat de ces insertions exporté pour la distribution du projet.

Le système de suivi par épisode fonctionne avec une granularité fine : marquage individuel, progression par saison calculée à la volée, et durée totale de visionnage agrégée depuis les durées stockées en base.

Les emails transactionnels sont opérationnels en production : vérification obligatoire du compte à l’inscription avant première connexion, réinitialisation de mot de passe fonctionnelle avec expiration de token, formulaire de contact accessible depuis plusieurs pages.

L’interface de paramètres permet de modifier le pseudo, le mot de passe, la date de naissance et l’avatar (10 avatars prédéfinis + avatar par défaut SVG).

Stack technique détaillée

PHP : backend et rendu serveur. Routing via PATH_INFO, gestion de session par token haché, requêtes PDO préparées, endpoints REST maison.

JavaScript / jQuery / Ajax : frontend. Interactions asynchrones sans rechargement de page, mise à jour d’état des épisodes et films.

MariaDB : persistance. 5 tables, contraintes d’unicité, clés étrangères pour l’intégrité référentielle.

TMDB API : source des métadonnées. Consommée via la bibliothèque Python tmdbv3api, mise en cache locale en base.

Python : scripts d’administration. Recherche TMDB, insertion des épisodes, téléchargement et validation des affiches (protection SSRF intégrée).

PHPMailer : emails transactionnels HTML. Vérification de compte, reset de mot de passe, demandes de contact. Configuré contre Stalwart.

Stalwart : serveur mail auto-hébergé. SMTP sortant pour les emails TVRecap, intégré à l’infrastructure existante.

Apache / mod_rewrite : URLs propres, séparation interface utilisateur / interface d’administration.

État actuel

L’application est en maintenance active. La démonstration publique est momentanément en pause pendant la migration vers une architecture conteneurisée.

La refonte prévue implique : passage à Docker Compose pour l’isolation des dépendances, séparation du frontend et du backend en services distincts, remplacement des appels shell_exec() vers Python par des endpoints internes propres. Aucune date de livraison fixée, ce projet évolue selon la disponibilité entre les autres chantiers en cours.

Ce que j’en retire

TVRecap est le projet qui a rendu réels des concepts jusqu’alors abstraits : la gestion de tokens de session, la différence entre expiration côté cookie et expiration côté base, pourquoi les tokens de réinitialisation doivent avoir une durée de vie courte, comment structurer une table de suivi pour qu’elle couvre deux types d’entités sans dupliquer le schéma.

La protection SSRF dans genImg.py est un exemple typique de sécurité apprise en faisant : le premier jet téléchargeait naïvement n’importe quelle URL. La liste de plages IP bloquées, la vérification du Content-Type de la réponse, et la validation de l’extension avant le téléchargement sont venues après avoir réalisé ce qu’un champ URL non contrôlé peut permettre.

La frustration face à l’architecture monolithique, difficile à tester, impossible à conteneuriser proprement sans refonte, est elle-même un apprentissage. Comprendre pourquoi une architecture pose problème demande de l’avoir vécue. C’est ce que ce projet a fourni.