forked from UKSOURCE/cms.hailearning.edu.vn
- 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
256 lines
7.6 KiB
JavaScript
256 lines
7.6 KiB
JavaScript
const { addBaseUrlToImages } = require("../utils/imageHelper");
|
|
const Footer = require("../models/footer");
|
|
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 {
|
|
const footer = await Footer.getSingle();
|
|
const processedData = addBaseUrlToImages(footer.toObject());
|
|
|
|
res.json(processedData);
|
|
} catch (error) {
|
|
console.error("Error getting footer:", error);
|
|
res.status(500).json({
|
|
error: "Failed to get footer data",
|
|
});
|
|
}
|
|
};
|
|
|
|
// PUT /api/admin/footer - Update toàn bộ footer cho CMS
|
|
exports.updateFooter = async (req, res) => {
|
|
try {
|
|
let updateData = req.body;
|
|
|
|
console.log("=== FOOTER UPDATE REQUEST RECEIVED ===");
|
|
console.log("Raw body:", JSON.stringify(req.body, null, 2));
|
|
|
|
// Nếu có footerJson, parse nó (tương tự Header logic)
|
|
if (updateData.footerJson && typeof updateData.footerJson === "string") {
|
|
try {
|
|
const parsedData = JSON.parse(updateData.footerJson);
|
|
console.log("✓ Parsed footerJson successfully:", parsedData);
|
|
updateData = parsedData;
|
|
} catch (e) {
|
|
console.error("✗ Error parsing footerJson:", e.message);
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: "Invalid JSON in footerJson: " + e.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
if (!footer) {
|
|
console.log("No existing footer found, creating new one");
|
|
footer = new Footer(updateData);
|
|
await footer.save();
|
|
console.log("✓ Footer created:", footer._id);
|
|
} else {
|
|
console.log("✓ Found existing footer:", footer._id);
|
|
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");
|
|
}
|
|
|
|
const processedData = addBaseUrlToImages(footer.toObject());
|
|
|
|
console.log("Updated footer data:", JSON.stringify(processedData, null, 2));
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Footer updated successfully",
|
|
data: processedData,
|
|
});
|
|
} catch (error) {
|
|
console.error("✗ Error updating footer:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: "Failed to update footer: " + error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Render admin view (giữ lại cho UI hiện tại)
|
|
exports.index = async (req, res) => {
|
|
try {
|
|
const data = await Footer.getSingle();
|
|
|
|
res.render("admin/footer/index", {
|
|
title: "Footer Management",
|
|
data: data.toObject(),
|
|
});
|
|
} catch (error) {
|
|
console.error("Error in footer index:", error);
|
|
req.flash("error_msg", "An error occurred while loading the page");
|
|
res.redirect("/admin/dashboard");
|
|
}
|
|
};
|
|
|
|
// Update method cho form hiện tại (giống Header pattern)
|
|
exports.update = async (req, res) => {
|
|
try {
|
|
let updateData = req.body;
|
|
|
|
console.log("=== FOOTER FORM UPDATE REQUEST RECEIVED ===");
|
|
console.log("Raw body:", JSON.stringify(req.body, null, 2));
|
|
|
|
// Nếu có footerJson, parse nó (giống Header logic)
|
|
if (updateData.footerJson && typeof updateData.footerJson === "string") {
|
|
try {
|
|
const parsedData = JSON.parse(updateData.footerJson);
|
|
console.log("✓ Parsed footerJson successfully:", parsedData);
|
|
updateData = parsedData;
|
|
} catch (e) {
|
|
console.error("✗ Error parsing footerJson:", e.message);
|
|
req.flash("error_msg", "Invalid JSON in footerJson: " + e.message);
|
|
return res.redirect("/admin/footer");
|
|
}
|
|
}
|
|
|
|
updateData = sanitizeFooterData(updateData);
|
|
|
|
// Lấy footer hiện tại hoặc tạo mới (giống Header)
|
|
let footer = await Footer.findOne();
|
|
|
|
// ✅ Capture BEFORE state
|
|
const beforeData = footer
|
|
? JSON.parse(JSON.stringify(footer.toObject()))
|
|
: {};
|
|
|
|
if (!footer) {
|
|
console.log("No existing footer found, creating new one");
|
|
footer = new Footer(updateData);
|
|
await footer.save();
|
|
console.log("✓ Footer created:", footer._id);
|
|
req.flash("success_msg", "Footer created successfully");
|
|
} else {
|
|
console.log("✓ Found existing footer:", footer._id);
|
|
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
|
|
const afterData = JSON.parse(JSON.stringify(footer.toObject()));
|
|
|
|
// ✅ AUDIT LOGGING - Footer Updated
|
|
const changes = diffObject(beforeData, afterData);
|
|
if (changes.length > 0) {
|
|
await writeAuditLog({
|
|
model: "Footer",
|
|
documentId: footer._id,
|
|
action: AUDIT_ACTIONS.UPDATE_FOOTER,
|
|
before: beforeData,
|
|
after: afterData,
|
|
changes,
|
|
req,
|
|
});
|
|
}
|
|
|
|
console.log("✓ Footer updated successfully");
|
|
req.flash("success_msg", "Footer updated successfully");
|
|
}
|
|
|
|
const activeTab = req.body.activeTab || "about";
|
|
res.redirect(`/admin/footer?activeTab=${activeTab}`);
|
|
} catch (err) {
|
|
console.error("✗ Error updating footer:", err);
|
|
req.flash("error_msg", err.message || "Error updating footer");
|
|
res.redirect("/admin/footer");
|
|
}
|
|
};
|
|
|
|
// Legacy API endpoints (giữ lại cho tương thích)
|
|
exports.api = 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",
|
|
});
|
|
}
|
|
};
|