feat: Add winners tab and 100% completion stats

Adds a new section to the admin control panel for managing yearly winners.
Also includes a new section on the user statistics page to display users
who have completed 100% of movies for each year.

Co-authored-by: mathieumoran1 <[email protected]>
This commit is contained in:
Cursor Agent
2025-12-21 20:38:01 +00:00
co-authored by mathieumoran1
parent 5a2b40357e
commit 9a7063b9dc
6 changed files with 666 additions and 6 deletions
+79
View File
@@ -167,6 +167,85 @@ router.get("/stats", verifyToken, async (req, res) => {
}
});
// Get users who have 100% completion per year (across all years in DB)
// Returns: { years: number[], totals: { [year]: number }, completersByYear: { [year]: {name, watchedCount, totalMoviesCount}[] } }
router.get("/completions", verifyToken, async (req, res) => {
try {
const yearsRaw = await Movie.distinct("year");
const years = (Array.isArray(yearsRaw) ? yearsRaw : [])
.map((y) => Number(y))
.filter((y) => parseOscarYear(y))
.sort((a, b) => b - a);
if (years.length === 0) {
return res.status(200).json({ years: [], totals: {}, completersByYear: {} });
}
// Fetch imdb_id + year for all movies that have a valid year
const movies = await Movie.find({ year: { $in: years } }).select("imdb_id year").lean();
const imdbIdsByYear = new Map();
const totals = {};
movies.forEach((m) => {
const year = Number(m?.year);
if (!parseOscarYear(year)) return;
const imdb = typeof m?.imdb_id === "string" ? m.imdb_id : "";
if (!imdb) return;
if (!imdbIdsByYear.has(year)) imdbIdsByYear.set(year, new Set());
imdbIdsByYear.get(year).add(imdb);
});
for (const y of years) {
const set = imdbIdsByYear.get(y);
totals[String(y)] = set ? set.size : 0;
}
const users = await User.find().select("name watchedMovies.imdb_id").lean();
const completersByYear = {};
for (const y of years) {
const total = totals[String(y)] || 0;
if (total <= 0) {
completersByYear[String(y)] = [];
continue;
}
const yearSet = imdbIdsByYear.get(y) || new Set();
const completers = [];
users.forEach((u) => {
const watched = Array.isArray(u?.watchedMovies) ? u.watchedMovies : [];
const watchedSet = new Set(
watched
.map((wm) => (typeof wm?.imdb_id === "string" ? wm.imdb_id : ""))
.filter(Boolean)
);
let count = 0;
for (const imdb of watchedSet) {
if (yearSet.has(imdb)) count += 1;
}
if (count === total) {
completers.push({
name: u?.name || "(sans nom)",
watchedCount: count,
totalMoviesCount: total,
});
}
});
completers.sort((a, b) => String(a.name).localeCompare(String(b.name), "fr", { sensitivity: "base" }));
completersByYear[String(y)] = completers;
}
return res.status(200).json({ years, totals, completersByYear });
} catch (err) {
console.error("Erreur dans l'extraction des completers:", err);
return res.status(500).json({ error: "Internal server error" });
}
});
// Register User
router.post("/register", async (req, res) => {
const { name, email, password, verifoof } = req.body;