forked from UKSOURCE/cms.hailearning.edu.vn
feat: implement comprehensive audit logging system
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
|
||||
const Home = require("../models/home");
|
||||
const Blog = require("../models/blog");
|
||||
const writeAuditLog = require("../audit/writeAuditLog");
|
||||
const diffObject = require("../audit/diffObject");
|
||||
const AUDIT_ACTIONS = require("../constants/auditAction");
|
||||
|
||||
// Các hàm hỗ trợ
|
||||
const getHomeDoc = async () => Home.findOne().sort({ updatedAt: -1 });
|
||||
@@ -30,10 +33,28 @@ const getDefaultHomeData = () => ({
|
||||
ctaButton: {},
|
||||
},
|
||||
visaSolutions: { heading: "", subheading: "", items: [] },
|
||||
visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} },
|
||||
testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] },
|
||||
visaCountries: {
|
||||
heading: "",
|
||||
subheading: "",
|
||||
description: "",
|
||||
countries: [],
|
||||
ctaButton: {},
|
||||
},
|
||||
testimonials: {
|
||||
heading: "",
|
||||
subheading: "",
|
||||
videoUrl: "",
|
||||
videoThumbnail: "",
|
||||
items: [],
|
||||
},
|
||||
videoGallery: { heading: "", videoUrl: "", thumbnail: "" },
|
||||
faq: { heading: "", subheading: "", description: "", ctaButton: {}, items: [] },
|
||||
faq: {
|
||||
heading: "",
|
||||
subheading: "",
|
||||
description: "",
|
||||
ctaButton: {},
|
||||
items: [],
|
||||
},
|
||||
achievements: { heading: "", subheading: "", items: [] },
|
||||
partners: { visaConsultancy: { items: [] }, brands: { items: [] } },
|
||||
blogPreview: {
|
||||
@@ -41,7 +62,7 @@ const getDefaultHomeData = () => ({
|
||||
subheading: "Visa Tips & Guides",
|
||||
ctaButton: { label: "View All Articles", href: "/blog" },
|
||||
items: [],
|
||||
selectedBlogIds: [] // Array of manually selected blog IDs
|
||||
selectedBlogIds: [], // Array of manually selected blog IDs
|
||||
},
|
||||
});
|
||||
|
||||
@@ -53,7 +74,7 @@ exports.index = async (req, res) => {
|
||||
|
||||
// Merge dữ liệu mặc định cho tất cả các phần
|
||||
const sections = Object.keys(defaults);
|
||||
sections.forEach(s => {
|
||||
sections.forEach((s) => {
|
||||
data[s] = data[s] || defaults[s];
|
||||
});
|
||||
|
||||
@@ -61,7 +82,9 @@ exports.index = async (req, res) => {
|
||||
const backendUrl = process.env.BACKEND_URL || "http://localhost:3001";
|
||||
|
||||
// Lấy tất cả blog để chọn trong CMS
|
||||
const allBlogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).lean();
|
||||
const allBlogs = await Blog.find({ status: "published" })
|
||||
.sort({ createdAt: -1 })
|
||||
.lean();
|
||||
|
||||
return res.render("admin/home/index", {
|
||||
layout: "layouts/main",
|
||||
@@ -85,17 +108,28 @@ exports.index = async (req, res) => {
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const sections = [
|
||||
"hero", "whyChooseUs", "visaSolutions", "visaCountries",
|
||||
"testimonials", "videoGallery", "faq", "achievements",
|
||||
"partners", "blogPreview"
|
||||
"hero",
|
||||
"whyChooseUs",
|
||||
"visaSolutions",
|
||||
"visaCountries",
|
||||
"testimonials",
|
||||
"videoGallery",
|
||||
"faq",
|
||||
"achievements",
|
||||
"partners",
|
||||
"blogPreview",
|
||||
];
|
||||
|
||||
let doc = await getHomeDoc();
|
||||
const beforeData = doc ? JSON.parse(JSON.stringify(doc.toObject())) : {};
|
||||
|
||||
if (!doc) {
|
||||
doc = new Home({});
|
||||
}
|
||||
|
||||
let hasChanges = false;
|
||||
const updatedSections = [];
|
||||
|
||||
for (const section of sections) {
|
||||
if (req.body[section]) {
|
||||
try {
|
||||
@@ -104,6 +138,7 @@ exports.update = async (req, res) => {
|
||||
doc[section] = payload;
|
||||
doc.markModified(section);
|
||||
hasChanges = true;
|
||||
updatedSections.push(section);
|
||||
} catch (e) {
|
||||
console.error(`Invalid JSON for ${section}:`, e);
|
||||
}
|
||||
@@ -116,6 +151,22 @@ exports.update = async (req, res) => {
|
||||
}
|
||||
|
||||
await doc.save();
|
||||
const afterData = JSON.parse(JSON.stringify(doc.toObject()));
|
||||
|
||||
// ✅ AUDIT LOGGING - Home Update
|
||||
const changes = diffObject(beforeData, afterData);
|
||||
if (changes.length > 0) {
|
||||
await writeAuditLog({
|
||||
model: "Home",
|
||||
documentId: doc._id,
|
||||
action: AUDIT_ACTIONS.UPDATE_HOME,
|
||||
before: beforeData,
|
||||
after: afterData,
|
||||
changes,
|
||||
req,
|
||||
});
|
||||
}
|
||||
|
||||
req.flash("success_msg", "Home page configuration has been updated!");
|
||||
return req.session.save(() => res.redirect("/admin/home"));
|
||||
} catch (err) {
|
||||
@@ -128,7 +179,10 @@ exports.update = async (req, res) => {
|
||||
// Public API// API lấy danh sách blog cho CMS
|
||||
exports.apiGetBlogs = async (req, res) => {
|
||||
try {
|
||||
const blogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).select("title slug featuredImage author publishedAt").lean();
|
||||
const blogs = await Blog.find({ status: "published" })
|
||||
.sort({ createdAt: -1 })
|
||||
.select("title slug featuredImage author publishedAt")
|
||||
.lean();
|
||||
res.json(blogs);
|
||||
} catch (err) {
|
||||
res.status(500).json({ error: err.message });
|
||||
@@ -137,7 +191,8 @@ exports.apiGetBlogs = async (req, res) => {
|
||||
exports.api = async (req, res) => {
|
||||
try {
|
||||
let data = await getHomeData();
|
||||
const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`;
|
||||
const baseUrl =
|
||||
process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`;
|
||||
|
||||
// === Xử lý Blog Preview động ===
|
||||
const blogPreview = data.blogPreview || {};
|
||||
@@ -147,12 +202,15 @@ exports.api = async (req, res) => {
|
||||
if (blogPreview.selectedBlogIds && blogPreview.selectedBlogIds.length > 0) {
|
||||
blogs = await Blog.find({
|
||||
_id: { $in: blogPreview.selectedBlogIds },
|
||||
status: "published"
|
||||
status: "published",
|
||||
}).lean();
|
||||
|
||||
// Sắp xếp theo thứ tự đã chọn trong selectedBlogIds
|
||||
blogs.sort((a, b) => {
|
||||
return blogPreview.selectedBlogIds.indexOf(a._id.toString()) - blogPreview.selectedBlogIds.indexOf(b._id.toString());
|
||||
return (
|
||||
blogPreview.selectedBlogIds.indexOf(a._id.toString()) -
|
||||
blogPreview.selectedBlogIds.indexOf(b._id.toString())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -165,18 +223,18 @@ exports.api = async (req, res) => {
|
||||
}
|
||||
|
||||
// Map dữ liệu blog sang format mà frontend mong đợi
|
||||
blogPreview.items = blogs.map(blog => ({
|
||||
blogPreview.items = blogs.map((blog) => ({
|
||||
title: blog.title,
|
||||
excerpt: blog.excerpt,
|
||||
category: blog.category && blog.category[0] ? blog.category[0] : "Visa",
|
||||
date: blog.publishedAt || blog.createdAt,
|
||||
author: {
|
||||
name: blog.author || "Admin",
|
||||
avatar: "" // Frontend đang tự xử lý hoặc dùng logo hệ thống
|
||||
avatar: "", // Frontend đang tự xử lý hoặc dùng logo hệ thống
|
||||
},
|
||||
comments: blog.commentsCount || 0,
|
||||
link: `/blog/${blog.slug}`,
|
||||
thumbnail: blog.featuredImage
|
||||
thumbnail: blog.featuredImage,
|
||||
}));
|
||||
|
||||
data.blogPreview = blogPreview;
|
||||
@@ -189,4 +247,3 @@ exports.api = async (req, res) => {
|
||||
return res.status(500).json({ error: "Error loading home data" });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user