commit 2f7c990376b9e868c67bf51e952981248d224198 Author: unfr Date: Thu Apr 23 08:33:37 2026 +0200 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc9cbc9 --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +# 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 : + +``` +13 13 * * * /home/matt/_WEB/proxytmdb/cron/run.sh > /home/matt/_WEB/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, remplace par : + +``` +13 13 * * * cd /home/matt/_WEB/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é. + +### `GET /search?query=` +Mêmes données que `/api?t=search` mais rendues en HTML (vignettes posters, +panneau d'info, liens IMDb/TMDb cliquables). Compat : `/search.php`. + +### `GET /` +Page d'index protégée par mot de passe + listing des fichiers du projet. + +## Architecture + +``` +proxytmdb/ +├── server.js # Bootstrap Fastify +├── config.js # Constantes, ports, chemins +├── lib/ +│ ├── paths.js # Layout disque //.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) +│ ├── http.js # fetch + retry + concurrence limitee +│ ├── format.js # Devises et runtime +│ ├── searchEngine.js # Pool de 8 workers persistants +│ └── searchWorker.js # Worker thread (1 chunk de la base) +├── routes/ +│ ├── index.js # Login + listing protege +│ ├── api.js # Endpoints JSON +│ └── search.js # Vue HTML +├── cron/ +│ ├── runAll.js # Pipeline complet +│ ├── 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 +├── test/ +│ └── helpers.test.js # Tests unitaires +├── tmdbintegral/ # Donnees (gitignore) +└── imdbratings.tsv # Donnees IMDb (gitignore) +``` + +## 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.