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;