const express = require("express"); const jwt = require("jsonwebtoken"); const OscarCategory = require("../models/OscarCategory"); const OscarPick = require("../models/OscarPick"); const User = require("../models/User"); const Setting = require("../models/Setting"); const router = express.Router(); const ACTIVE_YEAR_KEY = "active_oscar_year"; async function getOrInitActiveYear() { const existing = await Setting.findOne({ key: ACTIVE_YEAR_KEY }); if (existing && typeof existing.value === "number") return existing.value; return new Date().getFullYear(); } // 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; next(); }); } function isAdmin(decoded) { return !!decoded?.admin; } 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; } // Get all categories for a year (for users to see what to pick) router.get("/categories", verifyToken, async (req, res) => { try { const year = parseOscarYear(req.query.year) || await getOrInitActiveYear(); const categories = await OscarCategory.find({ year }) .sort({ categoryNumber: 1 }) .lean(); res.json({ year, categories }); } catch (err) { console.error("Error fetching categories:", err); res.status(500).json({ error: err.message }); } }); // Submit picks (user) router.post("/submit", verifyToken, async (req, res) => { try { const { year, picks } = req.body; const userId = req.user.id; const yearNum = parseOscarYear(year) || await getOrInitActiveYear(); if (!Array.isArray(picks) || picks.length === 0) { return res.status(400).json({ message: "Picks array is required" }); } // Verify categories exist for this year const categories = await OscarCategory.find({ year: yearNum }); if (categories.length === 0) { return res.status(400).json({ message: "No categories found for this year. Please import categories first." }); } // Validate picks const categoryMap = new Map(categories.map(c => [c.categoryNumber, c])); for (const pick of picks) { if (!pick.categoryNumber || !pick.selectedNominee) { return res.status(400).json({ message: "Each pick must have categoryNumber and selectedNominee" }); } const category = categoryMap.get(pick.categoryNumber); if (!category) { return res.status(400).json({ message: `Category ${pick.categoryNumber} not found` }); } const nomineeExists = category.nominees.some(n => n.name === pick.selectedNominee); if (!nomineeExists) { return res.status(400).json({ message: `Nominee "${pick.selectedNominee}" not found in category ${pick.categoryNumber}` }); } } // Create or update picks const pickData = { userId, year: yearNum, picks: picks.map(p => ({ categoryNumber: p.categoryNumber, categoryName: categoryMap.get(p.categoryNumber).categoryName, selectedNominee: p.selectedNominee })) }; const existingPick = await OscarPick.findOne({ userId, year: yearNum }); if (existingPick) { existingPick.picks = pickData.picks; existingPick.submittedAt = new Date(); await existingPick.save(); return res.json({ message: "Picks updated successfully", pick: existingPick }); } else { const newPick = new OscarPick(pickData); await newPick.save(); return res.json({ message: "Picks submitted successfully", pick: newPick }); } } catch (err) { console.error("Error submitting picks:", err); res.status(500).json({ error: err.message }); } }); // Get user's own picks router.get("/my-picks", verifyToken, async (req, res) => { try { const userId = req.user.id; const year = parseOscarYear(req.query.year) || await getOrInitActiveYear(); const pick = await OscarPick.findOne({ userId, year }) .populate('userId', 'name email') .lean(); if (!pick) { return res.json({ pick: null, year }); } res.json({ pick, year }); } catch (err) { console.error("Error fetching user picks:", err); res.status(500).json({ error: err.message }); } }); // Get all picks (admin only) - for viewing all users' picks router.get("/all", verifyToken, async (req, res) => { try { if (!isAdmin(req.user)) { return res.status(403).json({ message: "You do not have admin privileges" }); } const year = parseOscarYear(req.query.year) || await getOrInitActiveYear(); const picks = await OscarPick.find({ year }) .populate('userId', 'name email') .sort({ submittedAt: -1 }) .lean(); // Also get categories to show nominee options const categories = await OscarCategory.find({ year }) .sort({ categoryNumber: 1 }) .lean(); res.json({ year, picks, categories }); } catch (err) { console.error("Error fetching all picks:", err); res.status(500).json({ error: err.message }); } }); // Calculate scores for all users (admin only) router.post("/calculate-scores", verifyToken, async (req, res) => { try { if (!isAdmin(req.user)) { return res.status(403).json({ message: "You do not have admin privileges" }); } const { year } = req.body; const yearNum = parseOscarYear(year) || await getOrInitActiveYear(); // Get all categories with winners marked const categories = await OscarCategory.find({ year: yearNum }).lean(); const categoryWinners = new Map(); categories.forEach(cat => { const winner = cat.nominees.find(n => n.isWinner); if (winner) { categoryWinners.set(cat.categoryNumber, winner.name); } }); if (categoryWinners.size === 0) { return res.status(400).json({ message: "No winners marked for any category. Please mark winners first." }); } // Get all picks for this year with user info const allPicks = await OscarPick.find({ year: yearNum }) .populate('userId', 'name email') .lean(); // Calculate scores const scores = []; for (const pick of allPicks) { let correctCount = 0; const pickDetails = []; pick.picks.forEach(p => { const correctWinner = categoryWinners.get(p.categoryNumber); const isCorrect = correctWinner && p.selectedNominee === correctWinner; if (isCorrect) correctCount++; pickDetails.push({ categoryNumber: p.categoryNumber, categoryName: p.categoryName, selectedNominee: p.selectedNominee, correctWinner: correctWinner || null, isCorrect }); }); // Update the pick with score await OscarPick.findByIdAndUpdate(pick._id, { score: correctCount }); scores.push({ userId: pick.userId?._id || pick.userId, userName: pick.userId?.name || 'Unknown', userEmail: pick.userId?.email || '', score: correctCount, totalCategories: categories.length, pickDetails }); } // Sort by score (descending) scores.sort((a, b) => b.score - a.score); res.json({ message: "Scores calculated successfully", year: yearNum, scores, totalCategories: categories.length, categoriesWithWinners: categoryWinners.size }); } catch (err) { console.error("Error calculating scores:", err); res.status(500).json({ error: err.message }); } }); // Get scores for all users (available to all authenticated users) router.get("/scores", verifyToken, async (req, res) => { try { const year = parseOscarYear(req.query.year) || await getOrInitActiveYear(); const picks = await OscarPick.find({ year }) .populate('userId', 'name email') .sort({ score: -1, submittedAt: 1 }) .lean(); const categories = await OscarCategory.find({ year }).lean(); const categoryWinners = new Map(); categories.forEach(cat => { const winner = cat.nominees.find(n => n.isWinner); if (winner) { categoryWinners.set(cat.categoryNumber, winner.name); } }); const scores = picks.map(pick => { let correctCount = 0; const pickDetails = []; // Create a map of all categories for reference const categoryMap = new Map(categories.map(c => [c.categoryNumber, c])); pick.picks.forEach(p => { const correctWinner = categoryWinners.get(p.categoryNumber); const isCorrect = correctWinner && p.selectedNominee === correctWinner; if (isCorrect) correctCount++; const category = categoryMap.get(p.categoryNumber); const allNominees = category ? category.nominees.map(n => n.name) : []; pickDetails.push({ categoryNumber: p.categoryNumber, categoryName: p.categoryName, selectedNominee: p.selectedNominee, correctWinner: correctWinner || null, isCorrect, allNominees: allNominees }); }); return { userId: pick.userId?._id || pick.userId, userName: pick.userId?.name || 'Unknown', userEmail: pick.userId?.email || '', score: pick.score !== null && pick.score !== undefined ? pick.score : correctCount, totalCategories: categories.length, pickDetails, submittedAt: pick.submittedAt }; }); scores.sort((a, b) => b.score - a.score); res.json({ year, scores, totalCategories: categories.length, categoriesWithWinners: categoryWinners.size }); } catch (err) { console.error("Error fetching scores:", err); res.status(500).json({ error: err.message }); } }); module.exports = router;