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", }); } };