const Header = require("../models/header"); const HeaderMenu = require("../models/headerMenu"); const writeAuditLog = require("../audit/writeAuditLog"); const diffObject = require("../audit/diffObject"); const AUDIT_ACTIONS = require("../constants/auditAction"); /** * Helper function to build a tree structure (Mirroring logic in headerMenuController) */ const buildTree = (items, parentId = null) => { const branch = []; const children = items.filter( (item) => String(item.parentId) === String(parentId) || (item.parentId === null && parentId === null), ); for (const child of children) { const item = child.toObject ? child.toObject() : { ...child }; const subChildren = buildTree(items, item._id); item.children = subChildren.length > 0 ? subChildren : []; branch.push(item); } return branch.sort((a, b) => a.order - b.order); }; // Admin: Render header management page exports.index = async (req, res) => { try { const header = await Header.findOne().sort({ order: 1 }); // Prepare data for view const data = header ? { logo: header.logo?.light || "", signInButton: header.signInButton || { label: "Sign In", href: "/signin", }, ctaButton: header.ctaButton || { label: "Request Info", href: "/request", }, } : { logo: "", signInButton: { label: "Sign In", href: "/signin" }, ctaButton: { label: "Request Info", href: "/request" }, }; const activeTab = req.query.tab || "logo"; // Always fetch menu items to ensure they are available even if the user // switches tabs client-side const items = await HeaderMenu.find().sort({ order: 1 }); const menuData = { flat: items, tree: buildTree(items), }; res.render("admin/header/index", { layout: "layouts/main", title: "Header Management", user: req.session.user || null, data: data, activeTab: activeTab, menuData: menuData, }); } catch (error) { console.error("Error loading header management:", error); res.status(500).render("page/error", { title: "Error", message: "Failed to load header management page", }); } }; // Admin: Get all headers (API) exports.getAll = async (req, res) => { try { const headers = await Header.find().sort({ order: 1 }); res.json({ success: true, data: headers, }); } catch (error) { res.status(500).json({ success: false, message: error.message, }); } }; // Admin: Get single header exports.show = async (req, res) => { try { const header = await Header.findById(req.params.id); if (!header) { return res.status(404).json({ success: false, message: "Header not found", }); } res.json({ success: true, data: header, }); } catch (error) { res.status(500).json({ success: false, message: error.message, }); } }; // Admin: Create header exports.store = async (req, res) => { try { const { logo, signInButton, ctaButton, status, order } = req.body; const header = new Header({ logo: logo ? { light: logo } : {}, signInButton: signInButton || { label: "Sign In", href: "/signin" }, ctaButton: ctaButton || {}, status: status || "active", order: order || 1, }); await header.save(); res.status(201).json({ success: true, message: "Header created successfully", data: header, }); } catch (error) { res.status(400).json({ success: false, message: error.message, }); } }; // Admin: Update header exports.update = async (req, res) => { try { const { logo, signInButton, ctaButton, status, order } = req.body; console.log("=== UPDATE REQUEST RECEIVED ==="); console.log("Raw body:", JSON.stringify(req.body, null, 2)); // Upsert logic: find existing header or prepare to create new one let header = await Header.findOne().sort({ order: 1 }); let headerId = header?._id; // Capture BEFORE state for audit logging const beforeData = header ? JSON.parse(JSON.stringify(header.toObject())) : {}; if (!header) { console.log("No existing header found, creating new one"); // Create new header document header = new Header({ logo: logo ? { light: logo } : {}, signInButton: signInButton || { label: "Sign In", href: "/signin" }, ctaButton: ctaButton || {}, status: status || "active", order: order || 1, }); await header.save(); console.log("✓ Header created:", header._id); // Audit log for creation const afterData = JSON.parse(JSON.stringify(header.toObject())); const changes = diffObject(beforeData, afterData); if (changes.length > 0) { await writeAuditLog({ model: "Header", documentId: header._id, action: AUDIT_ACTIONS.UPDATE_HEADER, before: beforeData, after: afterData, changes, req, }); } return res.json({ success: true, message: "Header created successfully", data: header, }); } // Prepare logo data - merge with existing data let logoData = header.logo || {}; if (logo) { logoData = { light: logo, dark: header.logo?.dark || "", alt: header.logo?.alt || "", }; } // Prepare update data const updateData = { logo: logoData, signInButton: signInButton || header.signInButton, ctaButton: ctaButton || header.ctaButton, }; if (status !== undefined) updateData.status = status; if (order !== undefined) updateData.order = order; console.log( "Preparing to update header with data:", JSON.stringify(updateData, null, 2), ); // Update existing header const updatedHeader = await Header.findByIdAndUpdate(headerId, updateData, { new: true, runValidators: true, }); if (!updatedHeader) { console.error("✗ Header not found with ID:", headerId); return res.status(404).json({ success: false, message: "Header not found", }); } // Capture AFTER state for audit logging const afterData = JSON.parse(JSON.stringify(updatedHeader.toObject())); // Audit logging - Header Updated const changes = diffObject(beforeData, afterData); if (changes.length > 0) { await writeAuditLog({ model: "Header", documentId: updatedHeader._id, action: AUDIT_ACTIONS.UPDATE_HEADER, before: beforeData, after: afterData, changes, req, }); } console.log("✓ Header updated successfully:", updatedHeader._id); console.log("Updated header data:", JSON.stringify(updatedHeader, null, 2)); res.json({ success: true, message: "Header updated successfully", data: updatedHeader, }); } catch (error) { console.error("✗ Error updating header:", error); res.status(400).json({ success: false, message: error.message, }); } }; // Admin: Update status exports.updateStatus = async (req, res) => { try { const { status } = req.body; if (!["active", "inactive"].includes(status)) { return res.status(400).json({ success: false, message: "Invalid status", }); } const header = await Header.findByIdAndUpdate( req.params.id, { status }, { new: true }, ); if (!header) { return res.status(404).json({ success: false, message: "Header not found", }); } res.json({ success: true, message: "Header status updated", data: header, }); } catch (error) { res.status(500).json({ success: false, message: error.message, }); } }; // Admin: Delete header exports.destroy = async (req, res) => { try { const header = await Header.findByIdAndDelete(req.params.id); if (!header) { return res.status(404).json({ success: false, message: "Header not found", }); } res.json({ success: true, message: "Header deleted successfully", }); } catch (error) { res.status(500).json({ success: false, message: error.message, }); } }; // Public API: Get header (returns header regardless of status) exports.api = async (req, res) => { try { const header = await Header.findOne().sort({ order: 1, }); if (!header) { return res.status(404).json({ success: false, message: "No header found", }); } const baseUrl = (process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`).replace(/\/$/, ''); const rawLogoPath = header.logo?.light || ''; const logoImage = rawLogoPath.startsWith('http') ? rawLogoPath : `${baseUrl}${rawLogoPath}`; res.json({ success: true, data: { logo: { image: logoImage, href: "/", }, signInButton: { label: header.signInButton?.label || "Sign In", href: header.signInButton?.href || "/signin", }, ctaButton: { label: header.ctaButton?.label || "Request Info", href: header.ctaButton?.href || "/request", }, status: header.status || "active", }, }); } catch (error) { res.status(500).json({ success: false, message: error.message, }); } };