const HeaderMenu = require("../models/HeaderMenu"); const slugify = require("slugify"); /** * Helper: Build tree structure from flat array */ const buildMenuTree = (items, parentId = null, isPublic = false) => { 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 }; // Clean data for public API if requested let cleanItem = item; if (isPublic) { cleanItem = { id: item._id, title: item.title, url: item.url, type: item.type }; } const subChildren = buildMenuTree(items, item._id, isPublic); cleanItem.children = subChildren.length > 0 ? subChildren : []; branch.push(cleanItem); } return branch.sort((a, b) => a.order - b.order); }; /** * Helper: Recursive delete children */ const deleteRecursive = async (parentId) => { const children = await HeaderMenu.find({ parentId }); for (const child of children) { await deleteRecursive(child._id); await HeaderMenu.findByIdAndDelete(child._id); } }; // 1. Render Menu Tab logic (called from Header index or directly if route allows) exports.renderMenuTab = async (req, res) => { try { const items = await HeaderMenu.find().sort({ order: 1 }); const menuTree = buildMenuTree(items); return { menuTree, flatItems: items }; } catch (error) { console.error("Error fetching menu items:", error); throw error; } }; // 2. Create Menu Item exports.createMenu = async (req, res) => { try { console.log('=== BACKEND: createMenu hit ==='); console.log('Body:', req.body); const { title, url, parentId, order, status, type } = req.body; const slug = slugify(title, { lower: true, strict: true }); const newItem = new HeaderMenu({ title, slug, url, parentId: parentId || null, order: order || 0, status: status || "active", type: type || "internal" }); const savedItem = await newItem.save(); console.log('=== MENU CREATED ===', savedItem); req.flash("success_msg", "Menu item created successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { console.error('=== CREATE MENU ERROR ===', error); req.flash("error_msg", "Failed to create menu item: " + error.message); res.redirect("/admin/header?tab=menu"); } }; // 3. Update Menu Item exports.updateMenu = async (req, res) => { try { const { id } = req.params; console.log('=== BACKEND: updateMenu hit ===', { id }); console.log('Body:', req.body); const { title, url, parentId, order, status, type } = req.body; const updateData = { url, parentId: parentId || null, order, status, type }; if (title) { updateData.title = title; updateData.slug = slugify(title, { lower: true, strict: true }); } const updated = await HeaderMenu.findByIdAndUpdate(id, updateData, { new: true }); if (!updated) { console.log('=== UPDATE MENU NOT FOUND ===', id); req.flash("error_msg", "Menu item not found"); } else { console.log('=== MENU UPDATED ===', updated); req.flash("success_msg", "Menu item updated successfully"); } res.redirect("/admin/header?tab=menu"); } catch (error) { console.error('=== UPDATE MENU ERROR ===', error); req.flash("error_msg", "Update failed: " + error.message); res.redirect("/admin/header?tab=menu"); } }; // 4. Delete Menu Item (Cascade delete children) exports.deleteMenu = async (req, res) => { try { const { id } = req.body; const menuId = id || req.params.id; console.log('=== BACKEND: deleteMenu hit ===', { menuId, body: req.body }); await deleteRecursive(menuId); await HeaderMenu.findByIdAndDelete(menuId); console.log('=== MENU DELETED ===', menuId); req.flash("success_msg", "Menu item and its sub-menu deleted successfully"); res.redirect("/admin/header?tab=menu"); } catch (error) { console.error('=== DELETE MENU ERROR ===', error); req.flash("error_msg", "Delete failed: " + error.message); res.redirect("/admin/header?tab=menu"); } }; // 5. Reorder Menu exports.reorderMenu = async (req, res) => { try { const { items } = req.body; // Array of { id, order, parentId } if (items && Array.isArray(items)) { const bulkOps = items.map(item => ({ updateOne: { filter: { _id: item.id }, update: { order: item.order, parentId: item.parentId || null } } })); await HeaderMenu.bulkWrite(bulkOps); return res.json({ success: true, message: "Reordered successfully" }); } res.status(400).json({ success: false, message: "Invalid data" }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }; // Public API: Get active menu as clean tree exports.api = async (req, res) => { try { const items = await HeaderMenu.find({ status: "active" }).sort({ order: 1 }); const tree = buildMenuTree(items, null, true); res.json({ success: true, data: tree }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } };