From d0edfe92789aee1eb705ccafc0230464e831fc78 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 21 Dec 2025 19:41:58 +0000 Subject: [PATCH 1/3] Refactor OMDb API handling and user registration verification Co-authored-by: mathieumoran1 --- routes/movies.js | 41 ++++++++++++++++++++++++++++++++++------- routes/users.js | 27 +++++++++++++++++++-------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/routes/movies.js b/routes/movies.js index e814175..bfcd595 100644 --- a/routes/movies.js +++ b/routes/movies.js @@ -45,7 +45,12 @@ function hasAnySource({ vod_link, video_src, embed_src }) { // Fetch movie details from OMDb API async function fetchMovieDetailsFromOmdb(imdb_id) { - const apiKey = process.env.OMDB_API; // Replace with your actual OMDb API key + const apiKey = typeof process.env.OMDB_API === "string" ? process.env.OMDB_API.trim() : ""; + if (!apiKey) { + const err = new Error("OMDb n'est pas configuré (env OMDB_API manquante)."); + err.code = "OMDB_NOT_CONFIGURED"; + throw err; + } const omdbUrl = `https://www.omdbapi.com/?i=${encodeURIComponent(imdb_id)}&apikey=${apiKey}`; try { @@ -204,7 +209,22 @@ router.post("/add", async (req, res) => { } // Fetch movie details from OMDb API - const movieDetails = await fetchMovieDetailsFromOmdb(imdb_id); + let movieDetails; + try { + movieDetails = await fetchMovieDetailsFromOmdb(imdb_id); + } catch (err) { + // If OMDb isn't configured, keep the feature usable: add with placeholders so admin can fill manually later. + if (err?.code === "OMDB_NOT_CONFIGURED") { + movieDetails = { + title: String(imdb_id || "").trim() || "Untitled", + description: "", + rating: "", + poster: "" + }; + } else { + throw err; + } + } // Proceed with adding the movie const movie = new Movie({ @@ -363,11 +383,18 @@ router.put("/:id", async (req, res) => { // If refreshOmdb is true, overwrite OMDb-derived fields from the imdb_id if (refreshOmdb) { - const details = await fetchMovieDetailsFromOmdb(movie.imdb_id); - movie.title = details.title; - movie.description = details.description; - movie.rating = details.rating; - movie.poster = details.poster; + try { + const details = await fetchMovieDetailsFromOmdb(movie.imdb_id); + movie.title = details.title; + movie.description = details.description; + movie.rating = details.rating; + movie.poster = details.poster; + } catch (err) { + if (err?.code === "OMDB_NOT_CONFIGURED") { + return res.status(503).json({ message: "OMDb n'est pas configuré (env OMDB_API manquante)." }); + } + throw err; + } } else { if (title !== undefined) movie.title = title; if (description !== undefined) movie.description = description; diff --git a/routes/users.js b/routes/users.js index b99d7a4..9468199 100644 --- a/routes/users.js +++ b/routes/users.js @@ -11,13 +11,21 @@ const router = express.Router(); // Set password for verification -const woof = process.env.DOG_NAMES ? process.env.DOG_NAMES.split(",") : []; +const woof = + typeof process.env.DOG_NAMES === "string" + ? process.env.DOG_NAMES.split(",").map((s) => s.trim()).filter(Boolean) + : []; const ACTIVE_YEAR_KEY = "active_oscar_year"; // Fetch movie details from OMDb API async function fetchMovieDetailsFromOmdb(imdb_id) { - const apiKey = process.env.OMDB_API; // Replace with your actual OMDb API key + const apiKey = typeof process.env.OMDB_API === "string" ? process.env.OMDB_API.trim() : ""; + if (!apiKey) { + const err = new Error("OMDb n'est pas configuré (env OMDB_API manquante)."); + err.code = "OMDB_NOT_CONFIGURED"; + throw err; + } const omdbUrl = `https://www.omdbapi.com/?i=${encodeURIComponent(imdb_id)}&apikey=${apiKey}`; try { @@ -161,7 +169,11 @@ router.get("/stats", verifyToken, async (req, res) => { // Register User router.post("/register", async (req, res) => { const { name, email, password, role, verifoof } = req.body; + const dogCheckEnabled = woof.length > 0; + function registerVerification() { + // If DOG_NAMES isn't configured, skip the "dog name" verification entirely. + if (!dogCheckEnabled) return true; return woof.includes(verifoof); } @@ -169,16 +181,16 @@ router.post("/register", async (req, res) => { if (!password) { throw new Error("Le mot de passe est obligatoire et ne peut être indéfini"); } - if (!verifoof) { - throw new Error("Le nom du chien doit être défini" ); + if (dogCheckEnabled && !verifoof) { + throw new Error("Le nom du chien doit être défini"); } if (!registerVerification()) { - return res.status(201).json({ message: "Mauvais nom de chien" }); + return res.status(400).json({ message: "Mauvais nom de chien" }); } const existingUser = await User.findOne({ email }); if (existingUser) { - return res.status(201).json({ message: "L'email existe déjà, veuillez en choisir un autre." }); + return res.status(409).json({ message: "L'email existe déjà, veuillez en choisir un autre." }); } // Hash password and create user @@ -188,8 +200,7 @@ router.post("/register", async (req, res) => { res.status(201).json({ message: "L'utilisateur s'est enregistré avec succès !" }); } catch (err) { - res.status(400).json({ error: err.message }); - res.status(403).json({ message: "Erreur interne lmao" }); + return res.status(400).json({ message: err?.message || "Erreur interne" }); } }); From 9e825eb1a6c5fba48cce277409c0db1618444c36 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 21 Dec 2025 19:52:08 +0000 Subject: [PATCH 2/3] Refactor version endpoint to improve error handling and caching Co-authored-by: mathieumoran1 --- routes/version.js | 105 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 26 deletions(-) diff --git a/routes/version.js b/routes/version.js index 21ee502..cee2fd2 100644 --- a/routes/version.js +++ b/routes/version.js @@ -4,16 +4,65 @@ const moment = require("moment-timezone"); const router = express.Router(); -let latestCommitCache = null; -let lastFetchedTime = 0; const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds +const TIMEZONE = "America/New_York"; + +// Cache is stored in the exact response shape expected by the client. +let latestCommitCache = null; + +// Avoid noisy logs when GitHub isn't configured or is temporarily unavailable. +let lastLogTime = 0; +const LOG_THROTTLE_MS = 60 * 60 * 1000; // 1h + +function nowFormatted() { + return moment(Date.now()).tz(TIMEZONE).format("YYYY-MM-DD HH:mm:ss"); +} + +function getGitHubConfig() { + const owner = typeof process.env.GITHUB_OWNER === "string" ? process.env.GITHUB_OWNER.trim() : ""; + const repo = typeof process.env.GITHUB_REPO === "string" ? process.env.GITHUB_REPO.trim() : ""; + return { owner, repo, configured: !!owner && !!repo }; +} + +function getPackageVersion() { + try { + // routes/version.js -> ../package.json + const pkg = require("../package.json"); + return typeof pkg?.version === "string" && pkg.version.trim() ? pkg.version.trim() : "dev"; + } catch (_) { + return "dev"; + } +} + +function setFallbackCache(message, { configured } = { configured: false }) { + latestCommitCache = { + version: getPackageVersion(), + message: message || "Version info not available.", + author: "", + date: nowFormatted(), + configured: !!configured, + source: "fallback", + }; +} + +function logThrottled(...args) { + const t = Date.now(); + if (t - lastLogTime < LOG_THROTTLE_MS) return; + lastLogTime = t; + // eslint-disable-next-line no-console + console.error(...args); +} async function fetchLatestCommitFromGitHub() { - const githubOwner = process.env.GITHUB_OWNER; - const githubRepo = process.env.GITHUB_REPO; + const { owner, repo, configured } = getGitHubConfig(); + if (!configured) { + // Never attempt GitHub calls if not configured. + setFallbackCache("Version endpoint is not configured.", { configured: false }); + return; + } try { - const url = `https://api.github.com/repos/${githubOwner}/${githubRepo}/commits`; + const url = `https://api.github.com/repos/${owner}/${repo}/commits`; const response = await axios.get(url, { headers: { "Content-Type": "application/json", @@ -23,40 +72,44 @@ async function fetchLatestCommitFromGitHub() { if (response.data && response.data.length > 0) { const latestCommit = response.data[0]; latestCommitCache = { - sha: latestCommit.sha, - message: latestCommit.commit.message, - author: latestCommit.commit.author.name, - date: latestCommit.commit.author.date, + version: String(latestCommit.sha || "").substring(0, 5), + message: latestCommit?.commit?.message || "", + author: latestCommit?.commit?.author?.name || "", + date: moment(latestCommit?.commit?.author?.date || Date.now()) + .tz(TIMEZONE) + .format("YYYY-MM-DD HH:mm:ss"), + configured: true, + source: "github", }; - lastFetchedTime = Date.now(); } else { throw new Error("No commits found."); } } catch (error) { - console.error("Error fetching commit:", error.message); + // Keep serving a non-500 response, and don't spam logs. + logThrottled("Error fetching commit:", error?.message || error); + setFallbackCache("GitHub commit data is not available.", { configured: true }); } } -// Initial fetch to populate cache -fetchLatestCommitFromGitHub(); - -// Refresh cache every 5 minutes -setInterval(fetchLatestCommitFromGitHub, CACHE_DURATION); +// Initialize cache (without crashing / spamming when not configured). +const initialCfg = getGitHubConfig(); +if (!initialCfg.configured) { + setFallbackCache("Version endpoint is not configured.", { configured: false }); +} else { + fetchLatestCommitFromGitHub(); + const timer = setInterval(fetchLatestCommitFromGitHub, CACHE_DURATION); + // Don't keep the process alive just for this polling. + if (typeof timer.unref === "function") timer.unref(); +} // Route to get the latest commit (cached) router.get("/", async (req, res) => { + // Always return something (client clears footer on non-2xx). if (!latestCommitCache) { - return res.status(500).json({ message: "Commit data is not available yet." }); + const cfg = getGitHubConfig(); + setFallbackCache("Version info not available.", { configured: cfg.configured }); } - - res.json({ - version: latestCommitCache.sha.substring(0, 5), - message: latestCommitCache.message, - author: latestCommitCache.author, - date: moment(latestCommitCache.date) - .tz("America/New_York") - .format("YYYY-MM-DD HH:mm:ss"), - }); + return res.status(200).json(latestCommitCache); }); module.exports = router; \ No newline at end of file From d87dff1c9d6a7dfa92a22c78dae4d7f450918f4f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 21 Dec 2025 19:55:23 +0000 Subject: [PATCH 3/3] Refactor user management UI and add delete functionality Co-authored-by: mathieumoran1 --- public/control.html | 101 ++++++++++++++----- public/js/app_control.js | 208 +++++++++++++++++++++++++++++++++------ routes/users.js | 41 ++++++++ 3 files changed, 297 insertions(+), 53 deletions(-) diff --git a/public/control.html b/public/control.html index cb0ee31..b98fd5b 100644 --- a/public/control.html +++ b/public/control.html @@ -172,30 +172,6 @@
-
-
-

Réinitialiser mot de passe utilisateur

-

- Génère un mot de passe temporaire aléatoire. L'utilisateur devra choisir un nouveau mot de passe à sa prochaine connexion. -

- - - -
-
- - -
- Important: le mot de passe temporaire est affiché une seule fois ici. Copie-le et envoie-le à l'utilisateur. -
-
-
- -
-
-
-
-
@@ -215,10 +191,11 @@ Nom Email Statut + Actions - Charge la liste… + Charge la liste…
@@ -340,6 +317,80 @@
+ + + + + +