Merge pull request 'feat: Enhance comment management functionality in blog module' (#15) from fea/thanh-02022026-news into main

Reviewed-on: UKSOURCE/cms.hailearning.edu.vn#15
This commit is contained in:
2026-02-04 08:37:50 +00:00
7 changed files with 850 additions and 23 deletions

View File

@@ -198,6 +198,21 @@ exports.edit = async (req, res) => {
const categories = await BlogCategory.getActive();
const tags = await BlogTag.getActive();
// Get all comments for this blog post (including pending, approved, rejected)
const allComments = await BlogComment.find({ postId: blog._id })
.sort({ createdAt: -1 })
.lean();
// Organize comments with replies
const parentComments = allComments.filter(c => !c.parentId);
const commentsWithReplies = parentComments.map(parent => {
const replies = allComments.filter(c => c.parentId && c.parentId.toString() === parent._id.toString());
return {
...parent,
replies: replies
};
});
const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:3000';
res.render('admin/blog/edit', {
@@ -206,6 +221,8 @@ exports.edit = async (req, res) => {
blog,
categories,
tags,
comments: commentsWithReplies,
commentsCount: allComments.length,
currentPath: req.path,
user: req.session.user,
frontendUrl
@@ -437,7 +454,7 @@ exports.apiShow = async (req, res) => {
// Create a comment (no moderation for now: default approved)
exports.apiCreateComment = async (req, res) => {
try {
const { authorName, content, parentId } = req.body || {};
const { authorName, authorEmail, authorPhone, authorAddress, authorDate, content, parentId } = req.body || {};
if (!authorName || !String(authorName).trim()) {
return res.status(400).json({
@@ -477,6 +494,10 @@ exports.apiCreateComment = async (req, res) => {
const newComment = await BlogComment.create({
postId: blog._id,
authorName: String(authorName).trim(),
...(authorEmail ? { authorEmail: String(authorEmail).trim() } : {}),
...(authorPhone ? { authorPhone: String(authorPhone).trim() } : {}),
...(authorAddress ? { authorAddress: String(authorAddress).trim() } : {}),
...(authorDate ? { authorDate: String(authorDate).trim() } : {}),
content: String(content).trim(),
parentId: parentObjectId,
status: "approved",
@@ -653,4 +674,129 @@ exports.apiTags = async (req, res) => {
}
};
// -------------------- Comment Management Controllers --------------------
// Approve a comment
exports.approveComment = async (req, res) => {
try {
const { blogId, commentId } = req.params;
const blog = await Blog.findById(blogId);
if (!blog) {
return res.status(404).json({
success: false,
message: 'Blog post not found'
});
}
const comment = await BlogComment.findById(commentId);
if (!comment || comment.postId.toString() !== blogId) {
return res.status(404).json({
success: false,
message: 'Comment not found'
});
}
comment.status = 'approved';
await comment.save();
res.json({
success: true,
message: 'Comment approved successfully'
});
} catch (err) {
console.error('Approve comment error:', err);
res.status(500).json({
success: false,
message: 'Error approving comment',
error: err.message || 'Error approving comment'
});
}
};
// Reject a comment
exports.rejectComment = async (req, res) => {
try {
const { blogId, commentId } = req.params;
const blog = await Blog.findById(blogId);
if (!blog) {
return res.status(404).json({
success: false,
message: 'Blog post not found'
});
}
const comment = await BlogComment.findById(commentId);
if (!comment || comment.postId.toString() !== blogId) {
return res.status(404).json({
success: false,
message: 'Comment not found'
});
}
comment.status = 'rejected';
await comment.save();
res.json({
success: true,
message: 'Comment rejected successfully'
});
} catch (err) {
console.error('Reject comment error:', err);
res.status(500).json({
success: false,
message: 'Error rejecting comment',
error: err.message || 'Error rejecting comment'
});
}
};
// Delete a comment
exports.deleteComment = async (req, res) => {
try {
const { blogId, commentId } = req.params;
const blog = await Blog.findById(blogId);
if (!blog) {
return res.status(404).json({
success: false,
message: 'Blog post not found'
});
}
const comment = await BlogComment.findById(commentId);
if (!comment || comment.postId.toString() !== blogId) {
return res.status(404).json({
success: false,
message: 'Comment not found'
});
}
// Delete the comment and all its replies
await BlogComment.deleteMany({
$or: [
{ _id: commentId },
{ parentId: commentId }
]
});
// Update blog comment count
const remainingComments = await BlogComment.countDocuments({ postId: blogId });
await Blog.updateOne({ _id: blogId }, { commentsCount: remainingComments });
res.json({
success: true,
message: 'Comment deleted successfully'
});
} catch (err) {
console.error('Delete comment error:', err);
res.status(500).json({
success: false,
message: 'Error deleting comment',
error: err.message || 'Error deleting comment'
});
}
};
module.exports = exports;