feat: enhance about us and footer CMS admin panels

- Improve aboutUs controller with better field handling
- Update footer controller with expanded content management
- Refine about admin view templates
- Update appointment and footer admin views
- Add about contract repair migration script
- Update about.json seed data
This commit is contained in:
Tống Thành Đạt
2026-04-10 22:32:51 +07:00
parent 51c6303437
commit c6a2d4a55d
8 changed files with 534 additions and 114 deletions

View File

@@ -4,6 +4,77 @@ const writeAuditLog = require("../audit/writeAuditLog");
const diffObject = require("../audit/diffObject");
const AUDIT_ACTIONS = require("../constants/auditAction");
const isPlainObject = (value) =>
value && typeof value === "object" && !Array.isArray(value);
const normalizeFooterImagePath = (value) => {
if (!value || typeof value !== "string") return "";
const trimmed = value.trim();
if (!trimmed) return "";
if (trimmed.startsWith("/uploads/footer/")) {
return trimmed;
}
if (trimmed.startsWith("uploads/footer/")) {
return `/${trimmed}`;
}
if (/^https?:\/\//i.test(trimmed)) {
try {
const parsed = new URL(trimmed);
if (parsed.pathname.startsWith("/uploads/footer/")) {
return parsed.pathname;
}
} catch (error) {
console.warn("Failed to parse footer image URL:", trimmed, error.message);
}
}
return trimmed;
};
const sanitizeFooterData = (data = {}) => {
const sanitized = JSON.parse(JSON.stringify(data || {}));
if (!sanitized.top) {
return sanitized;
}
if (typeof sanitized.top.bgImage === "string") {
sanitized.top.bgImage = normalizeFooterImagePath(sanitized.top.bgImage);
}
if (sanitized.top.logo && typeof sanitized.top.logo.src === "string") {
sanitized.top.logo.src = normalizeFooterImagePath(sanitized.top.logo.src);
}
return sanitized;
};
const mergeFooterData = (currentValue, incomingValue) => {
if (incomingValue === undefined) {
return currentValue;
}
if (Array.isArray(incomingValue)) {
return incomingValue;
}
if (isPlainObject(currentValue) && isPlainObject(incomingValue)) {
const merged = { ...currentValue };
Object.keys(incomingValue).forEach((key) => {
merged[key] = mergeFooterData(currentValue[key], incomingValue[key]);
});
return merged;
}
return incomingValue;
};
// GET /api/footer - Public API cho website và CMS load dữ liệu
exports.getFooter = async (req, res) => {
try {
@@ -42,6 +113,8 @@ exports.updateFooter = async (req, res) => {
}
}
updateData = sanitizeFooterData(updateData);
// Lấy footer hiện tại hoặc tạo mới (giống Header logic)
let footer = await Footer.findOne();
@@ -52,8 +125,9 @@ exports.updateFooter = async (req, res) => {
console.log("✓ Footer created:", footer._id);
} else {
console.log("✓ Found existing footer:", footer._id);
// Merge với dữ liệu cũ thay vì overwrite (giống Header)
Object.assign(footer, updateData);
const mergedData = mergeFooterData(footer.toObject(), updateData);
// Use set() so nested footer fields persist correctly in MongoDB
footer.set(mergedData);
await footer.save();
console.log("✓ Footer updated successfully");
}
@@ -80,11 +154,10 @@ exports.updateFooter = async (req, res) => {
exports.index = async (req, res) => {
try {
const data = await Footer.getSingle();
const processedData = addBaseUrlToImages(data.toObject());
res.render("admin/footer/index", {
title: "Footer Management",
data: processedData,
data: data.toObject(),
});
} catch (error) {
console.error("Error in footer index:", error);
@@ -114,6 +187,8 @@ exports.update = async (req, res) => {
}
}
updateData = sanitizeFooterData(updateData);
// Lấy footer hiện tại hoặc tạo mới (giống Header)
let footer = await Footer.findOne();
@@ -130,8 +205,9 @@ exports.update = async (req, res) => {
req.flash("success_msg", "Footer created successfully");
} else {
console.log("✓ Found existing footer:", footer._id);
// Merge với dữ liệu cũ (giống Header)
Object.assign(footer, updateData);
const mergedData = mergeFooterData(footer.toObject(), updateData);
// Use set() so nested footer fields persist correctly in MongoDB
footer.set(mergedData);
await footer.save();
// ✅ Capture AFTER state
@@ -166,4 +242,14 @@ exports.update = async (req, res) => {
// Legacy API endpoints (giữ lại cho tương thích)
exports.api = exports.getFooter;
exports.getFooterData = exports.getFooter;
exports.getFooterData = async (req, res) => {
try {
const footer = await Footer.getSingle();
res.json(footer.toObject());
} catch (error) {
console.error("Error getting footer data for admin:", error);
res.status(500).json({
error: "Failed to get footer data",
});
}
};