Files
cms.uldp.edu.vn/controllers/aboutUsController.js
2026-04-10 15:55:15 +07:00

287 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { addBaseUrlToImages } = require("../utils/imageHelper");
const AboutUs = require("../models/aboutUs");
const Blog = require("../models/blog");
const jsonHelper = require("../utils/jsonHelper");
const {
validateLengthRules,
summarizeLengthErrors,
} = require("../utils/lengthValidation");
const {
ABOUT_US_LENGTH_RULES,
} = require("../constants/contentLengthRules");
const writeAuditLog = require("../audit/writeAuditLog");
const diffObject = require("../audit/diffObject");
const AUDIT_ACTIONS = require("../constants/auditAction");
const handleLengthValidation = (validation, req, res, options = {}) => {
const message =
summarizeLengthErrors(validation, 3) || "One or more fields exceed the allowed length.";
if (options.json) {
return res.status(400).json({
success: false,
error: message,
errors: validation.errors,
});
}
req.flash("error_msg", message);
return res.redirect(options.redirectTo || "/admin/about-us");
};
/**
* GET /api/about
* Lấy dữ liệu About Us (Public API cho website và CMS load dữ liệu)
*/
exports.getAbout = async (req, res) => {
try {
// Force no-cache headers
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
res.setHeader("Pragma", "no-cache");
res.setHeader("Expires", "0");
const data = await AboutUs.getSingle();
const rawData = data.toObject();
// === Dynamic Blog News Section ===
const news = rawData.news || {};
let blogs = [];
// Nếu có chọn blog cụ thể
if (news.selectedBlogIds && news.selectedBlogIds.length > 0) {
blogs = await Blog.find({
_id: { $in: news.selectedBlogIds },
status: "published",
}).lean();
// Sắp xếp theo thứ tự đã chọn trong selectedBlogIds
blogs.sort((a, b) => {
return (
news.selectedBlogIds.indexOf(a._id.toString()) -
news.selectedBlogIds.indexOf(b._id.toString())
);
});
}
// Nếu không chọn hoặc chọn nhưng không đủ, lấy thêm 3 bài mới nhất
if (blogs.length === 0) {
blogs = await Blog.find({ status: "published" })
.sort({ createdAt: -1 })
.limit(3)
.lean();
}
// Map dữ liệu blog sang format mà frontend mong đợi
news.items = blogs.map((blog) => ({
title: blog.title,
category: blog.category && blog.category[0] ? blog.category[0] : "Visa",
date:
blog.publishedAt ||
new Date(blog.createdAt).toLocaleDateString("en-GB", {
day: "numeric",
month: "long",
year: "numeric",
}),
comments: blog.commentsCount || 0,
author: {
name: blog.author || "Admin",
avatar: "/assets/img/home-1/news/client.png", // Default avatar
},
link: `/blog/${blog.slug}`,
thumbnail: blog.featuredImage,
}));
rawData.news = news;
// ===============================
const baseUrl =
process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`;
const processedData = addBaseUrlToImages(rawData, baseUrl);
res.json(processedData);
} catch (error) {
console.error("Error getting about data:", error);
res.status(500).json({
success: false,
error: "Failed to get about data",
});
}
};
/**
* PUT /api/about
* Cập nhật dữ liệu About Us (Dùng cho AJAX từ CMS)
*/
exports.updateAbout = async (req, res) => {
try {
let updateData = req.body;
// Nếu dữ liệu gửi qua trường aboutJson (dạng string JSON)
if (updateData.aboutJson && typeof updateData.aboutJson === "string") {
try {
updateData = JSON.parse(updateData.aboutJson);
} catch (e) {
return res.status(400).json({
success: false,
message: "Invalid JSON in aboutJson",
});
}
}
const doc = await AboutUs.getSingle();
// ✅ Capture BEFORE state
const beforeData = JSON.parse(JSON.stringify(doc.toObject()));
const validation = validateLengthRules(updateData, ABOUT_US_LENGTH_RULES);
if (!validation.valid) {
return handleLengthValidation(validation, req, res, { json: true });
}
// Use .set() for better handling of nested objects/arrays in Mongoose
doc.set(updateData);
await doc.save();
// ✅ Capture AFTER state
const afterData = JSON.parse(JSON.stringify(doc.toObject()));
// ✅ AUDIT LOGGING - About Us Updated
const changes = diffObject(beforeData, afterData);
if (changes.length > 0) {
await writeAuditLog({
model: "AboutUs",
documentId: doc._id,
action: AUDIT_ACTIONS.UPDATE_ABOUT_US,
before: beforeData,
after: afterData,
changes,
req,
});
console.log(
`✅ Audit log created for About Us update: ${changes.length} changes`,
);
} else {
console.log(" No changes detected for About Us update");
}
// Fetch fresh data for syncing and returning
const finalData = await AboutUs.findOne()
.select("-_id -__v -createdAt -updatedAt")
.lean();
// Update about.json file to keep it in sync
jsonHelper.writeJsonFile("about", finalData);
res.json({
success: true,
message: "About Us updated successfully",
data: finalData,
});
} catch (error) {
console.error("Error updating about data:", error);
res.status(500).json({
success: false,
error: "Failed to update about data: " + error.message,
});
}
};
/**
* Render admin page (Dùng cho Admin UI)
*/
exports.index = async (req, res) => {
try {
const data = await AboutUs.getSingle();
const rawData = data.toObject();
// Lấy tất cả blog để chọn trong CMS
const allBlogs = await Blog.find({ status: "published" })
.sort({ createdAt: -1 })
.lean();
const activeTab = req.query.activeTab || "hero";
res.render("admin/aboutUs/index", {
layout: "layouts/main",
title: "About Us Management",
data: rawData,
allBlogs,
activeTab,
user: req.session.user,
currentPath: req.path,
frontendUrl: process.env.FRONTEND_URL || "http://localhost:3000",
backendUrl: process.env.BACKEND_URL || "http://localhost:3001",
});
} catch (err) {
console.error("Error in about index:", err);
req.flash("error_msg", "Error loading About Us page");
res.redirect("/admin/dashboard");
}
};
/**
* Update method cho form-based submission (Admin UI - Post fallback)
*/
exports.update = async (req, res) => {
try {
let updateData = req.body;
if (updateData.aboutJson && typeof updateData.aboutJson === "string") {
try {
updateData = JSON.parse(updateData.aboutJson);
} catch (e) {
req.flash("error_msg", "Invalid JSON data");
return res.redirect("/admin/about-us");
}
}
const doc = await AboutUs.getSingle();
// ✅ Capture BEFORE state
const beforeData = JSON.parse(JSON.stringify(doc.toObject()));
const validation = validateLengthRules(updateData, ABOUT_US_LENGTH_RULES);
if (!validation.valid) {
return handleLengthValidation(validation, req, res, {
redirectTo: `/admin/about-us?activeTab=${req.query.activeTab || "hero"}`,
});
}
doc.set(updateData);
await doc.save();
// ✅ Capture AFTER state
const afterData = JSON.parse(JSON.stringify(doc.toObject()));
// ✅ AUDIT LOGGING - About Us Updated
const changes = diffObject(beforeData, afterData);
if (changes.length > 0) {
await writeAuditLog({
model: "AboutUs",
documentId: doc._id,
action: AUDIT_ACTIONS.UPDATE_ABOUT_US,
before: beforeData,
after: afterData,
changes,
req,
});
}
const finalData = await AboutUs.findOne()
.select("-_id -__v -createdAt -updatedAt")
.lean();
jsonHelper.writeJsonFile("about", finalData);
req.flash("success_msg", "About Us updated successfully");
const activeTab = req.query.activeTab || "hero";
res.redirect(`/admin/about-us?activeTab=${activeTab}`);
} catch (err) {
console.error("Update error:", err);
req.flash("error_msg", "Error updating About Us: " + err.message);
res.redirect("/admin/about-us");
}
};
// Aliases for compatibility
exports.api = exports.getAbout;
exports.page = exports.getAbout;
exports.updateAboutUs = exports.updateAbout;