From b00280e9e4307b1fed224bf495751374e7192736 Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Thu, 22 Jan 2026 14:26:43 -0500 Subject: [PATCH] Add CAM tagging to movies Store a CAM flag in the movie model and expose it via the admin add/edit flow, then render a CAM badge on movie posters in the films page. --- models/Movie.js | 1 + public/control.html | 12 ++++++++++++ public/css/app.css | 4 ++++ public/js/app_control.js | 9 +++++++-- public/js/app_movies.js | 16 ++++++++++------ routes/movies.js | 8 ++++++-- 6 files changed, 40 insertions(+), 10 deletions(-) diff --git a/models/Movie.js b/models/Movie.js index 5083547..407ed1a 100644 --- a/models/Movie.js +++ b/models/Movie.js @@ -6,6 +6,7 @@ const movieSchema = new mongoose.Schema({ description: { type: String, required: false }, rating: { type: String, required: false }, poster: { type: String, required: false }, + cam: { type: Boolean, required: false, default: false }, // Oscar pool year / edition (used to segment movies + stats) year: { type: Number, required: false, index: true }, category: { type: String, required: true }, diff --git a/public/control.html b/public/control.html index a4fa9fc..c83ed79 100644 --- a/public/control.html +++ b/public/control.html @@ -653,6 +653,12 @@ +
+
+ + +
+
@@ -1314,6 +1320,12 @@
+
+
+ + +
+
diff --git a/public/css/app.css b/public/css/app.css index 8bb9df4..d94328e 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -220,6 +220,10 @@ footer { z-index: 2; } + .cam-badge { + z-index: 2; + } + /* Stats → "Films regardés" modal */ .stats-movies-grid { display: grid; diff --git a/public/js/app_control.js b/public/js/app_control.js index 5038f72..4b61ff8 100644 --- a/public/js/app_control.js +++ b/public/js/app_control.js @@ -118,6 +118,7 @@ window.onload = async function () { const editRatingEl = document.getElementById('edit_rating'); const editPosterEl = document.getElementById('edit_poster'); const editDescriptionEl = document.getElementById('edit_description'); + const editCamFlagEl = document.getElementById('edit_cam_flag'); let moviesById = new Map(); let activeYear = null; @@ -1921,6 +1922,7 @@ window.onload = async function () { const year = parseYear(yearInput.value); const imdb_id = document.getElementById('imdb_id').value.trim(); const category = document.getElementById('category').value.trim(); + const cam = !!document.getElementById('cam_flag')?.checked; const vod_link = document.getElementById('vod_link').value.trim(); const player_mode = (document.getElementById('player_mode')?.value || 'auto').trim(); const video_src = document.getElementById('video_src')?.value?.trim() || ''; @@ -1944,7 +1946,7 @@ window.onload = async function () { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, - body: JSON.stringify({ year, imdb_id, category, vod_link, player_mode, video_src, video_file, embed_src }) + body: JSON.stringify({ year, imdb_id, category, vod_link, player_mode, video_src, video_file, embed_src, cam }) }); if (res.ok) { @@ -2199,6 +2201,7 @@ window.onload = async function () { editRatingEl.value = movie.rating || ''; editPosterEl.value = movie.poster || ''; editDescriptionEl.value = movie.description || ''; + if (editCamFlagEl) editCamFlagEl.checked = !!movie.cam; if (editMovieModalTitleEl) { const label = movie.title ? `Modifier: ${movie.title}` : 'Modifier le film'; @@ -2221,6 +2224,7 @@ window.onload = async function () { const video_file = (editVideoFileEl?.value || '').trim(); const embed_src = (editEmbedSrcEl?.value || '').trim(); const refreshOmdb = !!editRefreshOmdbEl.checked; + const cam = !!editCamFlagEl?.checked; const title = editTitleEl.value; const rating = editRatingEl.value; @@ -2255,7 +2259,8 @@ window.onload = async function () { video_src, video_file, embed_src, - refreshOmdb + refreshOmdb, + cam }; // Only send manual fields if we're not refreshing from OMDb diff --git a/public/js/app_movies.js b/public/js/app_movies.js index ddd1c43..25dc4c6 100644 --- a/public/js/app_movies.js +++ b/public/js/app_movies.js @@ -448,6 +448,14 @@ window.addEventListener('DOMContentLoaded', async function () { } } + const camBadge = movie?.cam ? 'CAM' : ''; + const posterBlock = ` +
+ ${camBadge} + ${movie.title} +
+ `; + const movieDiv = document.createElement('div'); movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card'); movieDiv.setAttribute('data-imdb-id', movie.imdb_id); @@ -457,13 +465,9 @@ window.addEventListener('DOMContentLoaded', async function () { ${ isClickable ? ` -
- ${movie.title} -
+ ${posterBlock}
` - : `
- ${movie.title} -
` + : `${posterBlock}` }
diff --git a/routes/movies.js b/routes/movies.js index 0c85045..43094e8 100644 --- a/routes/movies.js +++ b/routes/movies.js @@ -148,7 +148,7 @@ router.get("/", async (req, res) => { const projection = isChecklist ? "imdb_id title" - : "imdb_id title description rating poster year category vod_link player_mode video_src embed_src video_file updatedAt createdAt"; + : "imdb_id title description rating poster cam year category vod_link player_mode video_src embed_src video_file updatedAt createdAt"; const cacheKey = `movies:list:${year || "all"}:${isChecklist ? "checklist" : "films"}`; const cached = cacheGet(cacheKey); @@ -333,7 +333,7 @@ router.patch("/users/updateWatchedMovies", async (req, res) => { // Add movie router.post("/add", async (req, res) => { - const { imdb_id, category, vod_link, year, player_mode, video_src, embed_src, video_file } = req.body; + const { imdb_id, category, vod_link, year, player_mode, video_src, embed_src, video_file, cam } = req.body; try { // Get the token from the Authorization header @@ -390,6 +390,7 @@ router.post("/add", async (req, res) => { description: movieDetails.description, rating: movieDetails.rating, poster: movieDetails.poster, + cam: !!cam, year: parsedYear, category, vod_link: normalizedVod || undefined, @@ -491,6 +492,7 @@ router.put("/:id", async (req, res) => { embed_src, video_file, refreshOmdb, + cam, } = req.body || {}; if (imdb_id !== undefined && imdb_id !== null && !isValidImdbId(imdb_id)) { @@ -557,6 +559,8 @@ router.put("/:id", async (req, res) => { } // If refreshOmdb is true, overwrite OMDb-derived fields from the imdb_id + if (cam !== undefined) movie.cam = !!cam; + if (refreshOmdb) { try { const details = await fetchMovieDetailsFromOmdb(movie.imdb_id);