forked from UKSOURCE/cms.hailearning.edu.vn
902 lines
24 KiB
JavaScript
902 lines
24 KiB
JavaScript
const Blog = require("../models/blog");
|
|
const BlogCategory = require("../models/blogCategory");
|
|
const BlogTag = require("../models/blogTag");
|
|
const BlogComment = require("../models/blogComment");
|
|
const RecentPost = require("../models/recentPost");
|
|
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
|
|
const slugify = require("slugify");
|
|
const writeAuditLog = require("../audit/writeAuditLog");
|
|
const diffObject = require("../audit/diffObject");
|
|
const AUDIT_ACTIONS = require("../constants/auditAction");
|
|
|
|
// -------------------- Helper Functions --------------------
|
|
|
|
// Generate slug from title
|
|
const generateSlug = (title) => {
|
|
return slugify(title, {
|
|
lower: true,
|
|
strict: true,
|
|
locale: "vi",
|
|
});
|
|
};
|
|
|
|
// Update category post counts
|
|
const updateCategoryPostCounts = async () => {
|
|
const categories = await BlogCategory.find();
|
|
for (const category of categories) {
|
|
await category.updatePostCount();
|
|
}
|
|
};
|
|
|
|
// Update tag post counts
|
|
const updateTagPostCounts = async () => {
|
|
const tags = await BlogTag.find();
|
|
for (const tag of tags) {
|
|
await tag.updatePostCount();
|
|
}
|
|
};
|
|
|
|
// -------------------- Admin Controllers --------------------
|
|
|
|
// Display blog management page
|
|
exports.index = async (req, res) => {
|
|
try {
|
|
const page = parseInt(req.query.page) || 1;
|
|
const limit = parseInt(req.query.limit) || 10;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// Build filter
|
|
const filter = {};
|
|
if (req.query.status) {
|
|
filter.status = req.query.status;
|
|
}
|
|
if (req.query.category) {
|
|
filter.category = req.query.category;
|
|
}
|
|
if (req.query.search) {
|
|
filter.$or = [
|
|
{ title: { $regex: req.query.search, $options: "i" } },
|
|
{ excerpt: { $regex: req.query.search, $options: "i" } },
|
|
];
|
|
}
|
|
|
|
// Get blogs with pagination
|
|
const blogs = await Blog.find(filter)
|
|
.sort({ createdAt: -1 })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.lean();
|
|
|
|
const totalBlogs = await Blog.countDocuments(filter);
|
|
const totalPages = Math.ceil(totalBlogs / limit);
|
|
|
|
// Get categories for filter
|
|
const categories = await BlogCategory.getActive();
|
|
|
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:3001";
|
|
|
|
res.render("admin/blog/index", {
|
|
layout: "layouts/main",
|
|
title: "Blog Management",
|
|
blogs,
|
|
categories,
|
|
frontendUrl,
|
|
backendUrl,
|
|
getFullImageUrl, // Truyền helper function vào template
|
|
pagination: {
|
|
current: page,
|
|
total: totalPages,
|
|
limit,
|
|
totalItems: totalBlogs,
|
|
},
|
|
query: req.query,
|
|
currentPath: req.path,
|
|
user: req.session.user,
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog index error:", err);
|
|
req.flash("error_msg", "Error loading blogs");
|
|
res.redirect("/admin/dashboard");
|
|
}
|
|
};
|
|
|
|
// Show create blog form
|
|
exports.create = async (req, res) => {
|
|
try {
|
|
const categories = await BlogCategory.getActive();
|
|
const tags = await BlogTag.getActive();
|
|
|
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:3001";
|
|
|
|
res.render("admin/blog/create", {
|
|
layout: "layouts/main",
|
|
title: "Create New Blog Post",
|
|
categories,
|
|
tags,
|
|
currentPath: req.path,
|
|
user: req.session.user,
|
|
frontendUrl,
|
|
backendUrl,
|
|
getFullImageUrl, // Truyền helper function vào template
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog create form error:", err);
|
|
req.flash("error_msg", "Error loading create form");
|
|
res.redirect("/admin/blog");
|
|
}
|
|
};
|
|
|
|
// Store new blog
|
|
exports.store = async (req, res) => {
|
|
try {
|
|
const {
|
|
title,
|
|
excerpt,
|
|
content,
|
|
category,
|
|
tags,
|
|
status,
|
|
isFeatured,
|
|
author,
|
|
galleryImages,
|
|
quote,
|
|
contentAfterQuote,
|
|
} = req.body;
|
|
|
|
// Generate slug
|
|
const slug = generateSlug(title);
|
|
|
|
// Check if slug exists
|
|
const existingBlog = await Blog.findOne({ slug });
|
|
if (existingBlog) {
|
|
req.flash("error_msg", "A blog post with this title already exists");
|
|
return res.redirect("/admin/blog/create");
|
|
}
|
|
|
|
// Create blog data
|
|
const blogData = {
|
|
title,
|
|
slug,
|
|
excerpt,
|
|
content,
|
|
category: category
|
|
? Array.isArray(category)
|
|
? category
|
|
: [category]
|
|
: [], // Array categories
|
|
tags: tags ? (Array.isArray(tags) ? tags : [tags]) : [],
|
|
status: status || "published",
|
|
isFeatured: isFeatured === "on",
|
|
author: author || "Admin",
|
|
galleryImages: galleryImages
|
|
? Array.isArray(galleryImages)
|
|
? galleryImages
|
|
: [galleryImages]
|
|
: [],
|
|
quote: quote || "",
|
|
contentAfterQuote: contentAfterQuote || "",
|
|
};
|
|
|
|
// Handle featured image - using featuredImageUrl from form (uploaded via AJAX)
|
|
if (req.body.featuredImageUrl) {
|
|
blogData.featuredImage = req.body.featuredImageUrl;
|
|
}
|
|
|
|
// Create blog
|
|
const blog = new Blog(blogData);
|
|
await blog.save();
|
|
|
|
// AUDIT LOGGING - Blog Created
|
|
await writeAuditLog({
|
|
model: "Blog",
|
|
documentId: blog._id,
|
|
action: AUDIT_ACTIONS.CREATE_BLOG,
|
|
before: null, // No before state for CREATE
|
|
after: JSON.parse(JSON.stringify(blog.toObject())),
|
|
changes: [], // No changes for CREATE
|
|
req,
|
|
});
|
|
|
|
// Update counts
|
|
await updateCategoryPostCounts();
|
|
await updateTagPostCounts();
|
|
await RecentPost.syncFromBlogs();
|
|
|
|
req.flash("success_msg", "Blog post created successfully");
|
|
res.redirect("/admin/blog");
|
|
} catch (err) {
|
|
console.error("Blog store error:", err);
|
|
req.flash("error_msg", "Error creating blog post");
|
|
res.redirect("/admin/blog/create");
|
|
}
|
|
};
|
|
|
|
// Show edit blog form
|
|
exports.edit = async (req, res) => {
|
|
try {
|
|
const blog = await Blog.findById(req.params.id);
|
|
|
|
if (!blog) {
|
|
req.flash("error_msg", "Blog post not found");
|
|
return res.redirect("/admin/blog");
|
|
}
|
|
|
|
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";
|
|
const backendUrl = process.env.BACKEND_URL || "http://localhost:3001";
|
|
|
|
res.render("admin/blog/edit", {
|
|
layout: "layouts/main",
|
|
title: "Edit Blog Post",
|
|
blog,
|
|
categories,
|
|
tags,
|
|
comments: commentsWithReplies,
|
|
commentsCount: allComments.length,
|
|
currentPath: req.path,
|
|
user: req.session.user,
|
|
frontendUrl,
|
|
backendUrl,
|
|
getFullImageUrl, // Truyền helper function vào template
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog edit form error:", err);
|
|
req.flash("error_msg", "Error loading blog post");
|
|
res.redirect("/admin/blog");
|
|
}
|
|
};
|
|
|
|
// Update blog
|
|
exports.update = async (req, res) => {
|
|
try {
|
|
const blog = await Blog.findById(req.params.id);
|
|
|
|
if (!blog) {
|
|
req.flash("error_msg", "Blog post not found");
|
|
return res.redirect("/admin/blog");
|
|
}
|
|
|
|
// Capture BEFORE state
|
|
const beforeData = JSON.parse(JSON.stringify(blog.toObject()));
|
|
|
|
const {
|
|
title,
|
|
excerpt,
|
|
content,
|
|
category,
|
|
tags,
|
|
status,
|
|
isFeatured,
|
|
author,
|
|
galleryImages,
|
|
quote,
|
|
contentAfterQuote,
|
|
} = req.body;
|
|
|
|
// Update blog data
|
|
blog.title = title;
|
|
blog.excerpt = excerpt;
|
|
blog.content = content;
|
|
blog.category = category
|
|
? Array.isArray(category)
|
|
? category
|
|
: [category]
|
|
: []; // Array categories
|
|
blog.tags = tags ? (Array.isArray(tags) ? tags : [tags]) : [];
|
|
blog.status = status || "published";
|
|
blog.isFeatured = isFeatured === "on";
|
|
blog.author = author || "Admin";
|
|
blog.galleryImages = galleryImages
|
|
? Array.isArray(galleryImages)
|
|
? galleryImages
|
|
: [galleryImages]
|
|
: [];
|
|
blog.quote = quote || "";
|
|
blog.contentAfterQuote = contentAfterQuote || "";
|
|
|
|
// Handle featured image - using featuredImageUrl from form (uploaded via AJAX)
|
|
if (req.body.featuredImageUrl) {
|
|
blog.featuredImage = req.body.featuredImageUrl;
|
|
}
|
|
|
|
// Generate new slug if title changed
|
|
const newSlug = generateSlug(title);
|
|
if (newSlug !== blog.slug) {
|
|
const existingBlog = await Blog.findOne({
|
|
slug: newSlug,
|
|
_id: { $ne: blog._id },
|
|
});
|
|
if (existingBlog) {
|
|
req.flash("error_msg", "A blog post with this title already exists");
|
|
return res.redirect(`/admin/blog/${blog._id}/edit`);
|
|
}
|
|
blog.slug = newSlug;
|
|
}
|
|
|
|
await blog.save();
|
|
|
|
// Capture AFTER state
|
|
const afterData = JSON.parse(JSON.stringify(blog.toObject()));
|
|
|
|
// AUDIT LOGGING - Blog Updated
|
|
const changes = diffObject(beforeData, afterData);
|
|
if (changes.length > 0) {
|
|
await writeAuditLog({
|
|
model: "Blog",
|
|
documentId: blog._id,
|
|
action: AUDIT_ACTIONS.UPDATE_BLOG,
|
|
before: beforeData,
|
|
after: afterData,
|
|
changes,
|
|
req,
|
|
});
|
|
}
|
|
|
|
// Update counts
|
|
await updateCategoryPostCounts();
|
|
await updateTagPostCounts();
|
|
await RecentPost.syncFromBlogs();
|
|
|
|
req.flash("success_msg", "Blog post updated successfully");
|
|
res.redirect("/admin/blog");
|
|
} catch (err) {
|
|
console.error("Blog update error:", err);
|
|
req.flash("error_msg", "Error updating blog post");
|
|
res.redirect(`/admin/blog/${req.params.id}/edit`);
|
|
}
|
|
};
|
|
|
|
// Delete blog
|
|
exports.destroy = async (req, res) => {
|
|
try {
|
|
const blog = await Blog.findById(req.params.id);
|
|
|
|
if (!blog) {
|
|
req.flash("error_msg", "Blog post not found");
|
|
return res.redirect("/admin/blog");
|
|
}
|
|
|
|
// ✅ Capture BEFORE state
|
|
const beforeData = JSON.parse(JSON.stringify(blog.toObject()));
|
|
|
|
await Blog.findByIdAndDelete(req.params.id);
|
|
|
|
// ✅ AUDIT LOGGING - Blog Deleted
|
|
await writeAuditLog({
|
|
model: "Blog",
|
|
documentId: req.params.id,
|
|
action: AUDIT_ACTIONS.DELETE_BLOG,
|
|
before: beforeData,
|
|
after: null, // No after state for DELETE
|
|
changes: [],
|
|
req,
|
|
});
|
|
|
|
// Update counts
|
|
await updateCategoryPostCounts();
|
|
await updateTagPostCounts();
|
|
await RecentPost.syncFromBlogs();
|
|
|
|
req.flash("success_msg", "Blog post deleted successfully");
|
|
res.redirect("/admin/blog");
|
|
} catch (err) {
|
|
console.error("Blog delete error:", err);
|
|
req.flash("error_msg", "Error deleting blog post");
|
|
res.redirect("/admin/blog");
|
|
}
|
|
};
|
|
|
|
// -------------------- Public API Controllers --------------------
|
|
|
|
// Get all published blogs for frontend
|
|
exports.api = async (req, res) => {
|
|
try {
|
|
const page = parseInt(req.query.page) || 1;
|
|
const limit = parseInt(req.query.limit) || 10;
|
|
const skip = (page - 1) * limit;
|
|
|
|
// Build filter
|
|
const filter = { status: "published" };
|
|
|
|
if (req.query.category) {
|
|
filter.category = { $in: [req.query.category] }; // Tìm trong array categories
|
|
}
|
|
|
|
if (req.query.tag) {
|
|
filter.tags = { $in: [req.query.tag] }; // Tìm trong array tags
|
|
}
|
|
|
|
if (req.query.search) {
|
|
filter.$or = [
|
|
{ title: { $regex: req.query.search, $options: "i" } },
|
|
{ excerpt: { $regex: req.query.search, $options: "i" } },
|
|
];
|
|
}
|
|
|
|
// Get blogs
|
|
const blogs = await Blog.find(filter)
|
|
.sort({ createdAt: -1 })
|
|
.skip(skip)
|
|
.limit(limit)
|
|
.lean();
|
|
|
|
const totalBlogs = await Blog.countDocuments(filter);
|
|
|
|
// Add base URL to images
|
|
const baseUrl =
|
|
process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`;
|
|
const processedBlogs = blogs.map((blog) =>
|
|
addBaseUrlToImages(blog, baseUrl),
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Blogs fetched successfully",
|
|
data: {
|
|
blogs: processedBlogs,
|
|
pagination: {
|
|
current: page,
|
|
total: Math.ceil(totalBlogs / limit),
|
|
limit,
|
|
totalItems: totalBlogs,
|
|
},
|
|
},
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading blogs",
|
|
error: err.message || "Error loading blogs",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get single blog by slug
|
|
exports.apiShow = async (req, res) => {
|
|
try {
|
|
const blog = await Blog.findOne({
|
|
slug: req.params.slug,
|
|
status: "published",
|
|
}).lean();
|
|
|
|
if (!blog) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Blog post not found",
|
|
});
|
|
}
|
|
|
|
// Get comments for this post (parent comments only)
|
|
const parentComments = await BlogComment.getApprovedByPost(blog._id);
|
|
|
|
// Get replies for each parent comment
|
|
const commentsWithReplies = await Promise.all(
|
|
parentComments.map(async (parentComment) => {
|
|
const replies = await BlogComment.getReplies(parentComment._id);
|
|
return {
|
|
...parentComment.toObject(),
|
|
replies: replies.map((reply) => reply.toObject()),
|
|
};
|
|
}),
|
|
);
|
|
|
|
// Flatten comments array (parent + replies)
|
|
const allComments = commentsWithReplies.flatMap((comment) => [
|
|
comment,
|
|
...comment.replies,
|
|
]);
|
|
|
|
// Add comments to blog
|
|
blog.comments = allComments;
|
|
// Keep commentsCount in sync for frontend
|
|
blog.commentsCount = allComments.length;
|
|
|
|
// Add base URL to images
|
|
const baseUrl =
|
|
process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`;
|
|
const processedBlog = addBaseUrlToImages(blog, baseUrl);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Blog post fetched successfully",
|
|
data: processedBlog,
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog show API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading blog post",
|
|
error: err.message || "Error loading blog post",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Create a comment (no moderation for now: default approved)
|
|
exports.apiCreateComment = async (req, res) => {
|
|
try {
|
|
const {
|
|
authorName,
|
|
authorEmail,
|
|
authorPhone,
|
|
authorAddress,
|
|
authorDate,
|
|
content,
|
|
parentId,
|
|
} = req.body || {};
|
|
|
|
if (!authorName || !String(authorName).trim()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "authorName is required",
|
|
});
|
|
}
|
|
|
|
if (!content || !String(content).trim()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "content is required",
|
|
});
|
|
}
|
|
|
|
const blog = await Blog.findOne({
|
|
slug: req.params.slug,
|
|
status: "published",
|
|
}).lean();
|
|
if (!blog) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Blog post not found",
|
|
});
|
|
}
|
|
|
|
// If replying, ensure parent exists and belongs to same post
|
|
let parentObjectId = null;
|
|
if (parentId) {
|
|
const parent = await BlogComment.findOne({
|
|
_id: parentId,
|
|
postId: blog._id,
|
|
}).lean();
|
|
if (!parent) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "Invalid parentId",
|
|
});
|
|
}
|
|
parentObjectId = parentId;
|
|
}
|
|
|
|
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",
|
|
});
|
|
|
|
// Keep counter roughly correct (also counts replies)
|
|
await Blog.updateOne({ _id: blog._id }, { $inc: { commentsCount: 1 } });
|
|
|
|
return res.json({
|
|
success: true,
|
|
message: "Comment created successfully",
|
|
data: newComment.toJSON(),
|
|
});
|
|
} catch (err) {
|
|
console.error("Create comment API error:", err);
|
|
return res.status(500).json({
|
|
success: false,
|
|
message: "Error creating comment",
|
|
error: err.message || "Error creating comment",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get featured blogs
|
|
exports.apiFeatured = async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 3;
|
|
|
|
const blogs = await Blog.getFeatured().limit(limit).lean();
|
|
|
|
// Add base URL to images
|
|
const baseUrl =
|
|
process.env.BACKEND_URL || `${req.protocol}://${req.get("port")}`;
|
|
const processedBlogs = blogs.map((blog) =>
|
|
addBaseUrlToImages(blog, baseUrl),
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Featured blogs fetched successfully",
|
|
data: processedBlogs,
|
|
});
|
|
} catch (err) {
|
|
console.error("Featured blogs API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading featured blogs",
|
|
error: err.message || "Error loading featured blogs",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get recent blogs
|
|
exports.apiRecent = async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit) || 5;
|
|
|
|
// Try to get from RecentPost first
|
|
let recentPosts = await RecentPost.getRecent(limit);
|
|
|
|
// If no recent posts, sync from blogs
|
|
if (recentPosts.length === 0) {
|
|
await RecentPost.syncFromBlogs(limit);
|
|
recentPosts = await RecentPost.getRecent(limit);
|
|
}
|
|
|
|
// Add base URL to images
|
|
const baseUrl =
|
|
process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`;
|
|
const processedPosts = recentPosts.map((post) =>
|
|
addBaseUrlToImages(post, baseUrl),
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Recent blogs fetched successfully",
|
|
data: processedPosts,
|
|
});
|
|
} catch (err) {
|
|
console.error("Recent blogs API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading recent blogs",
|
|
error: err.message || "Error loading recent blogs",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get categories of a specific blog post
|
|
exports.apiCategories = async (req, res) => {
|
|
try {
|
|
const mongoose = require("mongoose");
|
|
let query;
|
|
|
|
// Check if it's a valid ObjectId
|
|
if (mongoose.Types.ObjectId.isValid(req.params.id)) {
|
|
query = { _id: req.params.id };
|
|
} else {
|
|
query = { slug: req.params.id };
|
|
}
|
|
|
|
query.status = "published";
|
|
|
|
const blog = await Blog.findOne(query).lean();
|
|
|
|
if (!blog) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Blog post not found",
|
|
});
|
|
}
|
|
|
|
// Get category details
|
|
const BlogCategory = require("../models/blogCategory");
|
|
const categories = await BlogCategory.find({
|
|
name: { $in: blog.category },
|
|
isActive: true,
|
|
}).lean();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Blog categories fetched successfully",
|
|
data: categories,
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog categories API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading blog categories",
|
|
error: err.message || "Error loading blog categories",
|
|
});
|
|
}
|
|
};
|
|
|
|
// Get tags of a specific blog post
|
|
exports.apiTags = async (req, res) => {
|
|
try {
|
|
const mongoose = require("mongoose");
|
|
let query;
|
|
|
|
// Check if it's a valid ObjectId
|
|
if (mongoose.Types.ObjectId.isValid(req.params.id)) {
|
|
query = { _id: req.params.id };
|
|
} else {
|
|
query = { slug: req.params.id };
|
|
}
|
|
|
|
query.status = "published";
|
|
|
|
const blog = await Blog.findOne(query).lean();
|
|
|
|
if (!blog) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "Blog post not found",
|
|
});
|
|
}
|
|
|
|
// Get tag details
|
|
const BlogTag = require("../models/blogTag");
|
|
const tags = await BlogTag.find({
|
|
name: { $in: blog.tags },
|
|
isActive: true,
|
|
}).lean();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Blog tags fetched successfully",
|
|
data: tags,
|
|
});
|
|
} catch (err) {
|
|
console.error("Blog tags API error:", err);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: "Error loading blog tags",
|
|
error: err.message || "Error loading blog tags",
|
|
});
|
|
}
|
|
};
|
|
|
|
// -------------------- 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;
|