Merge pull request #72 from quoije/2026

Promote 2026 updates to prod
This commit was merged in pull request #72.
This commit is contained in:
Mathieu Moran
2026-01-22 14:28:54 -05:00
committed by GitHub
6 changed files with 40 additions and 10 deletions
+1
View File
@@ -6,6 +6,7 @@ const movieSchema = new mongoose.Schema({
description: { type: String, required: false }, description: { type: String, required: false },
rating: { type: String, required: false }, rating: { type: String, required: false },
poster: { 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) // Oscar pool year / edition (used to segment movies + stats)
year: { type: Number, required: false, index: true }, year: { type: Number, required: false, index: true },
category: { type: String, required: true }, category: { type: String, required: true },
+12
View File
@@ -653,6 +653,12 @@
<label for="category" class="form-label">Catégorie</label> <label for="category" class="form-label">Catégorie</label>
<input type="text" class="form-control" id="category" placeholder="Best Picture, etc." required> <input type="text" class="form-control" id="category" placeholder="Best Picture, etc." required>
</div> </div>
<div class="col-12 col-md-4 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="cam_flag">
<label class="form-check-label" for="cam_flag">Tag CAM</label>
</div>
</div>
<div class="col-12"> <div class="col-12">
<div class="accordion" id="adminAddMovieAccordion"> <div class="accordion" id="adminAddMovieAccordion">
@@ -1314,6 +1320,12 @@
<label for="edit_rating" class="form-label">Rating (imdbRating)</label> <label for="edit_rating" class="form-label">Rating (imdbRating)</label>
<input type="text" class="form-control" id="edit_rating" placeholder="7.9"> <input type="text" class="form-control" id="edit_rating" placeholder="7.9">
</div> </div>
<div class="col-md-6 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="edit_cam_flag">
<label class="form-check-label" for="edit_cam_flag">Tag CAM</label>
</div>
</div>
<div class="col-12"> <div class="col-12">
<div class="form-check"> <div class="form-check">
+4
View File
@@ -220,6 +220,10 @@ footer {
z-index: 2; z-index: 2;
} }
.cam-badge {
z-index: 2;
}
/* Stats → "Films regardés" modal */ /* Stats → "Films regardés" modal */
.stats-movies-grid { .stats-movies-grid {
display: grid; display: grid;
+7 -2
View File
@@ -118,6 +118,7 @@ window.onload = async function () {
const editRatingEl = document.getElementById('edit_rating'); const editRatingEl = document.getElementById('edit_rating');
const editPosterEl = document.getElementById('edit_poster'); const editPosterEl = document.getElementById('edit_poster');
const editDescriptionEl = document.getElementById('edit_description'); const editDescriptionEl = document.getElementById('edit_description');
const editCamFlagEl = document.getElementById('edit_cam_flag');
let moviesById = new Map(); let moviesById = new Map();
let activeYear = null; let activeYear = null;
@@ -1921,6 +1922,7 @@ window.onload = async function () {
const year = parseYear(yearInput.value); const year = parseYear(yearInput.value);
const imdb_id = document.getElementById('imdb_id').value.trim(); const imdb_id = document.getElementById('imdb_id').value.trim();
const category = document.getElementById('category').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 vod_link = document.getElementById('vod_link').value.trim();
const player_mode = (document.getElementById('player_mode')?.value || 'auto').trim(); const player_mode = (document.getElementById('player_mode')?.value || 'auto').trim();
const video_src = document.getElementById('video_src')?.value?.trim() || ''; const video_src = document.getElementById('video_src')?.value?.trim() || '';
@@ -1944,7 +1946,7 @@ window.onload = async function () {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` '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) { if (res.ok) {
@@ -2199,6 +2201,7 @@ window.onload = async function () {
editRatingEl.value = movie.rating || ''; editRatingEl.value = movie.rating || '';
editPosterEl.value = movie.poster || ''; editPosterEl.value = movie.poster || '';
editDescriptionEl.value = movie.description || ''; editDescriptionEl.value = movie.description || '';
if (editCamFlagEl) editCamFlagEl.checked = !!movie.cam;
if (editMovieModalTitleEl) { if (editMovieModalTitleEl) {
const label = movie.title ? `Modifier: ${movie.title}` : 'Modifier le film'; 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 video_file = (editVideoFileEl?.value || '').trim();
const embed_src = (editEmbedSrcEl?.value || '').trim(); const embed_src = (editEmbedSrcEl?.value || '').trim();
const refreshOmdb = !!editRefreshOmdbEl.checked; const refreshOmdb = !!editRefreshOmdbEl.checked;
const cam = !!editCamFlagEl?.checked;
const title = editTitleEl.value; const title = editTitleEl.value;
const rating = editRatingEl.value; const rating = editRatingEl.value;
@@ -2255,7 +2259,8 @@ window.onload = async function () {
video_src, video_src,
video_file, video_file,
embed_src, embed_src,
refreshOmdb refreshOmdb,
cam
}; };
// Only send manual fields if we're not refreshing from OMDb // Only send manual fields if we're not refreshing from OMDb
+10 -6
View File
@@ -448,6 +448,14 @@ window.addEventListener('DOMContentLoaded', async function () {
} }
} }
const camBadge = movie?.cam ? '<span class="badge bg-warning text-dark position-absolute top-0 end-0 m-2 cam-badge">CAM</span>' : '';
const posterBlock = `
<div class="text-center pt-3 px-3 position-relative movie-poster-wrap">
${camBadge}
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
</div>
`;
const movieDiv = document.createElement('div'); const movieDiv = document.createElement('div');
movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card'); movieDiv.classList.add('col-12', 'col-sm-6', 'col-lg-4', 'movie-card');
movieDiv.setAttribute('data-imdb-id', movie.imdb_id); movieDiv.setAttribute('data-imdb-id', movie.imdb_id);
@@ -457,13 +465,9 @@ window.addEventListener('DOMContentLoaded', async function () {
${ ${
isClickable isClickable
? `<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none"> ? `<a href="${playerUrl}" target="_self" rel="noopener noreferrer" class="text-decoration-none">
<div class="text-center pt-3 px-3"> ${posterBlock}
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
</div>
</a>` </a>`
: `<div class="text-center pt-3 px-3"> : `${posterBlock}`
<img src="${movie.poster}" class="img-fluid rounded movie-poster" alt="${movie.title}">
</div>`
} }
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<div class="d-flex justify-content-between align-items-start gap-2"> <div class="d-flex justify-content-between align-items-start gap-2">
+6 -2
View File
@@ -148,7 +148,7 @@ router.get("/", async (req, res) => {
const projection = isChecklist const projection = isChecklist
? "imdb_id title" ? "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 cacheKey = `movies:list:${year || "all"}:${isChecklist ? "checklist" : "films"}`;
const cached = cacheGet(cacheKey); const cached = cacheGet(cacheKey);
@@ -333,7 +333,7 @@ router.patch("/users/updateWatchedMovies", async (req, res) => {
// Add movie // Add movie
router.post("/add", async (req, res) => { 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 { try {
// Get the token from the Authorization header // Get the token from the Authorization header
@@ -390,6 +390,7 @@ router.post("/add", async (req, res) => {
description: movieDetails.description, description: movieDetails.description,
rating: movieDetails.rating, rating: movieDetails.rating,
poster: movieDetails.poster, poster: movieDetails.poster,
cam: !!cam,
year: parsedYear, year: parsedYear,
category, category,
vod_link: normalizedVod || undefined, vod_link: normalizedVod || undefined,
@@ -491,6 +492,7 @@ router.put("/:id", async (req, res) => {
embed_src, embed_src,
video_file, video_file,
refreshOmdb, refreshOmdb,
cam,
} = req.body || {}; } = req.body || {};
if (imdb_id !== undefined && imdb_id !== null && !isValidImdbId(imdb_id)) { 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 refreshOmdb is true, overwrite OMDb-derived fields from the imdb_id
if (cam !== undefined) movie.cam = !!cam;
if (refreshOmdb) { if (refreshOmdb) {
try { try {
const details = await fetchMovieDetailsFromOmdb(movie.imdb_id); const details = await fetchMovieDetailsFromOmdb(movie.imdb_id);