# proxytmdb Proxy/cache local de l'API TMDB enrichi des notes IMDb, avec recherche intelligente par titre/année/épisode. 100% Node.js (Fastify + worker_threads). Conçu pour servir une base TMDB complète (~1.7M entrées movies + tv) sans appel API à chaque requête : tout est mis en cache sur disque par un cron quotidien et indexé en mémoire. ## Stack - **Node.js 20+** (ESM, fetch natif, worker_threads, Intl.NumberFormat) - **Fastify 5** + `@fastify/secure-session` + `@fastify/static` - Aucune base de données — système de fichiers `//.json` ## Installation ```bash npm install cp .env.example .env # Edite .env : TMDB_API_KEY, PROXYTMDB_PASSWORD, SESSION_SECRET ``` ## Démarrage du serveur ```bash npm start ``` Le `.env` est chargé automatiquement (Node 20+ `--env-file-if-exists`). ### Variables d'environnement Obligatoires (sans valeur par défaut) : | Variable | Rôle | |------------------------|------------------------------------------| | `TMDB_API_KEY` | Clé API TMDB | | `PROXYTMDB_PASSWORD` | Mot de passe page d'index | | `SESSION_SECRET` | Clé de signature des sessions (32 chars) | Optionnelles (voir [.env.example](.env.example) pour la liste complète) : `PORT`, `HOST`, `PAGE_TITLE`, URLs externes, paramètres de recherche (`TITLE_TOLERANCE`, `LEV_*`, `YEAR_TOLERANCE`...). ## Cron (mise à jour des données) Pipeline complet : ```bash npm run cron ``` Étapes individuelles disponibles : ```bash npm run cron:imdb # title.ratings.tsv depuis datasets.imdbws.com npm run cron:tmdb # sync incrementale TMDB (movie + tv) npm run cron:justwatch # watch providers npm run cron:tmdb2imdb # mappings bidirectionnels TMDb <-> IMDb npm run cron:search # construction des chunks de recherche npm run cron:ambiguity # detection des doublons (CSV) ``` Crontab système recommandée (remplace `/chemin/vers/proxytmdb` par le chemin absolu d'installation) : ``` 13 13 * * * /chemin/vers/proxytmdb/cron/run.sh > /chemin/vers/proxytmdb/lastcron.txt 2>&1 ``` Le wrapper [cron/run.sh](cron/run.sh) charge nvm puis lance Node sur la version `nvm alias default` — pratique si tu mets à jour Node via nvm, le cron suivra automatiquement. Si tu n'utilises pas nvm : ``` 13 13 * * * cd /chemin/vers/proxytmdb && /usr/bin/node --env-file-if-exists=.env cron/runAll.js > lastcron.txt 2>&1 ``` ## Endpoints HTTP ### `GET /api?t=movie&q=` Retourne le JSON TMDB complet (cache local) avec les notes IMDb fusionnées (`note_imdb`, `vote_imdb`). ### `GET /api?t=tv&q=` Idem pour les séries. ### `GET /api?t=search&q=` Recherche par titre/année/épisode. Parse la requête (extraction année, extraction épisode `SxxExx`/`partN`/`NxN`/`Exxx`), choisit movie ou tv selon la présence d'un épisode, calcule les distances Levenshtein UTF-8 sur les titres FR/EN/VO, retourne les meilleurs résultats triés par score puis popularité puis écart d'année. Réponse JSON : ```json { "results": [ { "title": "Inception", "english_title": "Inception", "original_title": "Inception", "years": 2010, "poster": "https://image.tmdb.org/t/p/w200/...", "genres": "Action Science-Fiction Aventure ", "countries": "GB US ", "runtime": "2 h 28 min", "imdb_id": "tt1375666", "imdb_url": "https://www.imdb.com/title/tt1375666", "note_imdb": 8.8, "vote_imdb": 2809792, "tmdb_id": 27205, "tmdb_url": "https://www.themoviedb.org/movie/27205", "note_tmdb": 8.4, "vote_tmdb": 39044, "budget": "$160,000,000", "revenue": "$839,030,630", "tagline": "...", "overview": "..." } ] } ``` L'ancien chemin `/api.php` est aussi exposé pour compatibilité. Réponses cachées en mémoire (LRU 1000 entrées, TTL 1h). Reload automatique des chunks de recherche après chaque cron (watcher `fs.watch`). ### `GET /api?t=imdb&q=` Lookup direct par IMDb ID (`tt0133093`). Renvoie le détail movie ou tv correspondant, avec note IMDb fusionnée. Utilise les mappings `imdb2movie.json` / `imdb2tv.json` chargés en mémoire. ### `GET /api?t=providers&type=movie|tv&q=` Watch providers JustWatch par pays (FR, US, etc.) — données déjà téléchargées par le cron, exposées sous le format TMDB original. ### `GET /` — Interface web SPA vanilla JS (zéro build), thème sombre. Une barre de recherche, grille de posters, modal de détail avec tagline/overview/budget/revenue/providers FR. Accepte requêtes texte (`Inception 2010`), IMDb IDs (`tt0133093`) et URLs TMDB collées (`themoviedb.org/movie/27205`). ### `GET /admin` Listing protégé des fichiers du projet (mot de passe argon2id). Pour générer un nouveau hash : ```bash node tools/hashPassword.js 'mon-mot-de-passe' ``` Puis copier la sortie dans `.env` sous `ADMIN_PASSWORD_HASH=`. ### `GET /health` JSON liveness/readiness : status, uptime, mémoire, nombre de notes IMDb chargées. Renvoie 503 si l'index IMDb n'a pas pu être chargé. ### `GET /metrics` Format Prometheus standard : `http_requests_total`, `http_request_duration_seconds`, `search_cache_hits_total`, `search_cache_misses_total`, `imdb_ratings_total`, `search_workers`, plus métriques process par défaut (CPU, RSS, event loop). ### Rate limit 50 requêtes/seconde par IP par défaut (configurable via `RATE_LIMIT_PER_SEC`). `/health` et `/metrics` exemptés. ## Architecture ``` proxytmdb/ ├── server.js # Bootstrap Fastify + rate limit + sessions ├── config.js # Constantes, ports, chemins, env vars ├── biome.json # Lint + format ├── public/ # SPA vanilla JS (UI publique) │ ├── index.html │ ├── style.css │ └── app.js ├── lib/ │ ├── paths.js # Layout //.json │ ├── mbLevenshtein.js # Levenshtein UTF-8 par codepoint │ ├── titleFilter.js # Translit ligatures + filtre Latin/chiffres │ ├── queryParser.js # Extraction annee/episode/titre │ ├── imdbRatings.js # Index IMDb en memoire (Map) │ ├── imdbMapping.js # Mapping IMDb -> TMDb id │ ├── http.js # fetch + retry + concurrence limitee │ ├── format.js # Devises et runtime │ ├── lockFile.js # Lock file PID-based pour le cron │ ├── password.js # Argon2id hash + verify │ ├── metrics.js # Counters/Histograms Prometheus │ ├── searchEngine.js # Pool workers + watcher reload chaud │ └── searchWorker.js # Worker thread (1 chunk de la base) ├── routes/ │ ├── api.js # /api (movie/tv/imdb/providers/search) + cache LRU │ ├── admin.js # /admin (login argon2 + listing) │ └── health.js # /health + /metrics ├── cron/ │ ├── runAll.js # Pipeline complet (avec lock file) │ ├── run.sh # Wrapper nvm pour crontab │ ├── imdbRatings.js # title.ratings.tsv.gz │ ├── tmdbExports.js # Exports quotidiens TMDB │ ├── tmdbSync.js # Sync incrementale via /changes │ ├── justwatchSync.js # /watch/providers │ ├── tmdb2imdb.js # Mappings bidirectionnels │ ├── buildSearch.js # Chunks de recherche │ └── ambiguity.js # Detection des doublons ├── tools/ │ └── hashPassword.js # Outil CLI pour generer un hash argon2 ├── test/ │ └── helpers.test.js # Tests unitaires ├── tmdbintegral/ # Donnees (gitignore) └── imdbratings.tsv # Donnees IMDb (gitignore) ``` ## Qualité de code Lint + format avec **Biome** (config dans `biome.json`) : ```bash npm run lint # Verifie sans modifier npm run format # Formate uniquement (write) npm run fix # Lint + format + auto-fix ``` ## Tests ```bash npm test ``` 13 tests unitaires sur les helpers (Levenshtein UTF-8, filtre titres, parser de requête, paths). La parité contre le PHP original a été validée pendant le portage sur 5 requêtes différentes (films, séries, ASCII, UTF-8, avec et sans année) — top-N IDs identiques dans le même ordre. ## Notes - Les fichiers `searchmovie.json` et `searchtv.json` sont des **chunks de recherche** générés par le cron. Le serveur en charge un par worker au démarrage (mémoire résidente, ~120 Mo total). - L'index IMDb (`imdbratings.tsv`, ~30 Mo, 1.66M lignes) est chargé une fois en `Map` au démarrage et rechargé automatiquement quand son `mtime` change (typiquement après le cron quotidien). - Le layout disque `//.json` est conservé à l'identique de l'ancienne version PHP : on peut basculer sans regénérer les ~1700 dossiers x 1000 fichiers existants. - Les 8 workers de recherche sont persistants (vs PHP qui forkait 8 processus à chaque requête). Gain mesuré : ~30% sur le temps de réponse.