// src/controllers/review.controller.js const Review = require('../models/Review'); const Media = require('../models/Media'); const User = require('../models/User'); // --- Helper: Recalculate Average Rating --- // We run this every time a review is added. // It uses MongoDB's math engine to average the scores instantly. const updateMediaStats = async (mediaId) => { const stats = await Review.aggregate([ { $match: { mediaId: mediaId } }, { $group: { _id: '$mediaId', avgRating: { $avg: '$rating' }, reviewCount: { $sum: 1 } } } ]); if (stats.length > 0) { await Media.findByIdAndUpdate(mediaId, { 'brutaleStats.avgRating': stats[0].avgRating.toFixed(1), 'brutaleStats.reviewCount': stats[0].reviewCount }); } }; // --- 1. POST: Create Review --- exports.createReview = async (req, res) => { try { const { mediaId, rating, content, guestName } = req.body; // COERCE isSpoiler safely const isSpoiler = (() => { const val = req.body.isSpoiler; if (typeof val === 'boolean') return val; if (typeof val === 'string') return val === 'true'; return false; })(); const userId = req.user ? req.user.id : null; // A. Validation if (!mediaId || !rating || !content) { return res.status(400).json({ error: 'Missing required fields' }); } // --- NEW: load username for logged-in user --- let finalGuestName = guestName; // default for guests if (userId) { const user = await User.findById(userId).select("username"); finalGuestName = user.username; // overwrite guestName } else { finalGuestName = guestName || "Anonymous Brutalist"; } // B. Anti-Spam const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000); const duplicateCheck = await Review.findOne({ mediaId, $or: [ { userId: userId }, { guestName: finalGuestName } ], createdAt: { $gte: tenMinutesAgo } }); if (duplicateCheck) { return res.status(429).json({ error: 'Chill! You just reviewed this. Wait 10 mins.' }); } // C. Build Review const reviewData = { mediaId, userId, rating, content, isSpoiler, guestName: finalGuestName, // <-- ALWAYS included slangTags: [] }; const newReview = await Review.create(reviewData); // D. Update user stats if (userId) { await User.findByIdAndUpdate(userId, { $inc: { 'stats.reviewsCount': 1 } }); } // E. Update media stats updateMediaStats(mediaId); res.status(201).json(newReview); } catch (error) { console.error('Review Error:', error); res.status(500).json({ error: 'Server crashed processing your review.' }); } }; // --- 2. GET: Fetch Reviews --- exports.getReviews = async (req, res) => { try { const { mediaId } = req.params; const page = parseInt(req.query.page) || 1; const limit = 10; const reviews = await Review.find({ mediaId }) .populate('userId', 'username honestyScore badges') // Get author details .sort({ createdAt: -1 }) // Newest first .skip((page - 1) * limit) .limit(limit); res.json(reviews); } catch (error) { res.status(500).json({ error: 'Failed to fetch reviews' }); } }; // --- 3. GET: Reviews by User --- exports.getReviewsByUser = async (req, res) => { try { const { userId } = req.params; const reviews = await Review.find({ userId }) .populate('mediaId', 'title posterPath type') // bring media info .sort({ createdAt: -1 }); res.json(reviews); } catch (error) { console.error("User Review Fetch Error:", error); res.status(500).json({ error: 'Failed to fetch user review history' }); } }; // ------------------- exports.addReply = async (req, res) => { try { const { id } = req.params; // Review ID const { content, guestName } = req.body; const userId = req.user ? req.user.id : null; if (!content) return res.status(400).json({ error: 'Content required' }); // 1. Find Review const review = await Review.findById(id); if (!review) return res.status(404).json({ error: 'Review not found' }); // 2. Determine reply name let finalGuestName; if (userId) { const user = await User.findById(userId).select("username"); finalGuestName = user.username; // logged in user = username } else { finalGuestName = guestName || "Anonymous Brutalist"; // guest user } // 3. Build reply object const newReply = { content, userId: userId || null, guestName: finalGuestName }; // 4. Add reply and save review.replies.push(newReply); await review.save(); // 5. Populate username immediately await review.populate("replies.userId", "username"); // 6. Get the reply we just pushed let added = review.replies[review.replies.length - 1]; // Normalize shape for frontend (safe, clean) const normalizedReply = { _id: added._id, content: added.content, createdAt: added.createdAt, username: added.userId ? added.userId.username : added.guestName, userId: added.userId?._id || null }; res.status(201).json(normalizedReply); } catch (error) { console.error("Reply Error:", error); res.status(500).json({ error: 'Failed to add reply' }); } };