Global movie year setting #6

Merged
quoije merged 1 commits from cursor/global-movie-year-setting-9879 into 2026 2025-12-20 17:36:34 -05:00
3 changed files with 98 additions and 0 deletions
Showing only changes of commit 64cfdc3c6d - Show all commits
+2
View File
@@ -20,11 +20,13 @@ mongoose
const userRoutes = require("./routes/users");
const movieRoutes = require("./routes/movies");
const versionRoutes = require("./routes/version");
const settingsRoutes = require("./routes/settings");
app.use(express.static('public'));
app.use("/api/users", userRoutes);
app.use("/api/movies", movieRoutes);
app.use("/api/version", versionRoutes);
app.use("/api/settings", settingsRoutes);
// Start Server
const PORT = process.env.PORT || 5000;
+14
View File
@@ -0,0 +1,14 @@
const mongoose = require("mongoose");
// Simple key/value store for global site settings.
// We currently use it for the "active Oscar year" that drives filtering.
const settingSchema = new mongoose.Schema(
{
key: { type: String, required: true, unique: true, index: true },
value: { type: mongoose.Schema.Types.Mixed, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model("Setting", settingSchema);
+82
View File
@@ -0,0 +1,82 @@
const express = require("express");
const jwt = require("jsonwebtoken");
const Setting = require("../models/Setting");
const Movie = require("../models/Movie");
const router = express.Router();
const ACTIVE_YEAR_KEY = "active_oscar_year";
function parseOscarYear(raw) {
if (raw === undefined || raw === null || raw === "") return null;
const n = Number(raw);
if (!Number.isInteger(n)) return null;
if (n < 1900 || n > 3000) return null;
return n;
}
async function getOrInitActiveYear() {
const existing = await Setting.findOne({ key: ACTIVE_YEAR_KEY });
if (existing && typeof existing.value === "number") return existing.value;
// Initialize from latest movie year if available, else from current year.
const latestMovie = await Movie.findOne({ year: { $type: "number" } })
.sort({ year: -1 })
.select("year");
const fallbackYear = latestMovie?.year || new Date().getFullYear();
const year = parseOscarYear(fallbackYear) || new Date().getFullYear();
await Setting.findOneAndUpdate(
{ key: ACTIVE_YEAR_KEY },
{ $set: { value: year } },
{ upsert: true, new: true }
);
return year;
}
// Public: get current active Oscar year used by the site.
router.get("/year", async (req, res) => {
try {
const year = await getOrInitActiveYear();
return res.status(200).json({ year });
} catch (err) {
return res.status(500).json({ error: err.message });
}
});
// Admin-only: set current active Oscar year.
router.put("/year", async (req, res) => {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ message: "Authentication token is required" });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (!decoded.admin) {
return res.status(403).json({ message: "You do not have admin privileges" });
}
const year = parseOscarYear(req.body?.year);
if (!year) {
return res.status(400).json({ message: "Année invalide (ex: 2026)" });
}
const updated = await Setting.findOneAndUpdate(
{ key: ACTIVE_YEAR_KEY },
{ $set: { value: year } },
{ upsert: true, new: true }
);
return res.status(200).json({ year: updated.value });
} catch (err) {
if (err instanceof jwt.JsonWebTokenError) {
return res.status(401).json({ message: "Invalid or expired token" });
}
return res.status(500).json({ error: err.message });
}
});
module.exports = router;