forked from UKSOURCE/cms.hailearning.edu.vn
add api headermenu and crud management
This commit is contained in:
@@ -1,4 +1,24 @@
|
||||
const Header = require("../models/header");
|
||||
const HeaderMenu = require("../models/HeaderMenu");
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
@@ -30,11 +50,23 @@ exports.index = async (req, res) => {
|
||||
logo: "",
|
||||
};
|
||||
|
||||
const activeTab = req.query.tab || "topbar";
|
||||
|
||||
// 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,
|
||||
user: req.session.user || null,
|
||||
data: data,
|
||||
activeTab: activeTab,
|
||||
menuData: menuData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error loading header management:", error);
|
||||
|
||||
175
controllers/headerMenuController.js
Normal file
175
controllers/headerMenuController.js
Normal file
@@ -0,0 +1,175 @@
|
||||
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 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user