Files
oscars-pool/routes/users.js
T
Cursor Agentandmathieumoran1 7ff02207f6 feat: Implement dynamic Oscar year selection
This commit introduces the ability to select and manage the active Oscar year. This change affects page titles, API requests, and data filtering across the application, ensuring that all views and statistics are relevant to the chosen year.

Co-authored-by: mathieumoran1 <[email protected]>
2025-12-20 22:48:11 +00:00

207 lines
6.6 KiB
JavaScript

const express = require("express");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const Movie = require("../models/Movie");
const Setting = require("../models/Setting");
const axios = require("axios");
const router = express.Router();
// Set password for verification
const woof = process.env.DOG_NAMES ? process.env.DOG_NAMES.split(",") : [];
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 omdbUrl = `https://www.omdbapi.com/?i=${encodeURIComponent(imdb_id)}&apikey=${apiKey}`;
try {
const response = await axios.get(omdbUrl);
if (response.data.Response === "True") {
return {
title: response.data.Title.replace(/'/g, "")
};
} else {
throw new Error('Movie not found on OMDb API');
}
} catch (error) {
throw new Error(`Error fetching movie details: ${error.message}`);
}
}
// Middleware to verify token
function verifyToken(req, res, next) {
const token = req.headers['authorization'] && req.headers['authorization'].startsWith('Bearer ')
? req.headers['authorization'].split(' ')[1]
: null;
if (!token) {
return res.status(403).json({ message: "No token provided, access denied." });
}
jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => {
if (err) {
return res.status(401).json({ message: "Invalid or expired token" });
}
req.user = decoded; // Store decoded user info for use in routes
next();
});
}
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;
}
// Get user stats with watched movie titles and details from the movies collection
router.get("/stats", verifyToken, async (req, res) => {
try {
// Fetch all users
const users = await User.find();
// If a year is explicitly provided, use it; otherwise default to the global active year.
const year = parseOscarYear(req.query.year) || await getOrInitActiveYear();
// Fetch movies (optionally filtered by year)
const allMovies = await Movie.find({ year });
const totalMoviesCount = allMovies.length;
if (totalMoviesCount === 0) {
return res.status(400).json({ message: "Aucun films disponible." });
}
// Build stats for each user
const userStats = users.map((user) => {
const movieByImdbId = new Map(allMovies.map((m) => [m.imdb_id, m]));
// Get watched movies with details from the database
const watchedMovies = user.watchedMovies
.map((wm) => movieByImdbId.get(wm.imdb_id)) // Match movie details
.filter(Boolean); // Remove any `undefined` values (if movie is not found)
const watchedCount = watchedMovies.length;
const watchedRatio = ((watchedCount / totalMoviesCount) * 100).toFixed(1);
const watchedMoviesDetails = watchedMovies.map((movie) => ({
imdb_id: movie.imdb_id,
title: movie.title
}));
return {
name: user.name,
year,
watchedCount,
totalMoviesCount,
watchedRatio: `${watchedRatio}%`,
watchedMovies: watchedMoviesDetails // Include movie details
};
});
res.json(userStats);
} catch (err) {
console.error("Erreur dans l'extraction des statistiques sur les utilisateurs :", err);
res.status(500).json({ error: "Internal server error" });
}
});
// Register User
router.post("/register", async (req, res) => {
const { name, email, password, role, verifoof } = req.body;
function registerVerification() {
return woof.includes(verifoof);
}
try {
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 (!registerVerification()) {
return res.status(201).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." });
}
// Hash password and create user
const hashedPassword = await bcrypt.hash(password, 10);
const user = new User({ name, email, password: hashedPassword, role });
await user.save();
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" });
}
});
// Login User
router.post("/login", async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) return res.status(404).json({ message: "L'utilisateur n'a pas été trouvé" });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(401).json({ message: "Informations d'identification non valides" });
console.log("[debug] " + user.name + " logged in")
// Check if the user role is 69 and set the admin flag
const isAdmin = user.role === 69;
// Create a token with the user ID, name, and admin flag if the user is an admin
const token = jwt.sign(
{ id: user._id, name: user.name, admin: isAdmin }, // Include name in the token payload
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.status(200).json({ token });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Protected route to get movies (example)
router.get("/movies", verifyToken, async (req, res) => {
// Only authorized users will reach this point
const movies = await Movie.find(); // Assuming Movie model exists
res.json(movies);
});
module.exports = router;