Brutale-api / src /controllers /review.controller.js
rohanshaw's picture
Upload 16 files
e557560 verified
// 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' });
}
};