forked from UKSOURCE/cms.hailearning.edu.vn
438 lines
11 KiB
JavaScript
438 lines
11 KiB
JavaScript
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
|
|
? {
|
|
topbar: {
|
|
contactInfo: {
|
|
phone: header.top?.phone || "",
|
|
email: header.top?.email || "",
|
|
location: header.top?.location || "",
|
|
},
|
|
socialLinks: header.top?.socialLinks || [],
|
|
},
|
|
logo: header.logo?.light || "",
|
|
}
|
|
: {
|
|
topbar: {
|
|
contactInfo: {
|
|
phone: "",
|
|
email: "",
|
|
location: "",
|
|
},
|
|
socialLinks: [],
|
|
},
|
|
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 || 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 { top, offcanvas, menu, logo, ctaButton, status, order } = req.body;
|
|
|
|
const header = new Header({
|
|
top,
|
|
offcanvas,
|
|
menu,
|
|
logo,
|
|
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 {
|
|
let { top, topbarJson, offcanvas, menu, logo, ctaButton, status, order } =
|
|
req.body;
|
|
|
|
console.log("=== UPDATE REQUEST RECEIVED ===");
|
|
console.log("Raw body:", JSON.stringify(req.body, null, 2));
|
|
console.log("topbarJson type:", typeof topbarJson);
|
|
console.log("topbarJson value:", topbarJson);
|
|
|
|
// Nếu có topbarJson, parse nó
|
|
if (topbarJson && typeof topbarJson === "string") {
|
|
try {
|
|
const parsedData = JSON.parse(topbarJson);
|
|
console.log("✓ Parsed topbarJson successfully:", parsedData);
|
|
// Chuyển đổi từ topbarData sang top format
|
|
top = {
|
|
phone: parsedData.contactInfo?.phone || "",
|
|
email: parsedData.contactInfo?.email || "",
|
|
location: parsedData.contactInfo?.location || "",
|
|
socialLinks: parsedData.socialLinks || [],
|
|
};
|
|
|
|
if (logo) {
|
|
updateData.logo = logoData;
|
|
}
|
|
|
|
console.log(
|
|
"Preparing to update header with data:",
|
|
JSON.stringify(updateData, null, 2),
|
|
);
|
|
|
|
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",
|
|
});
|
|
}
|
|
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,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Nếu không có id, tìm header đầu tiên hoặc tạo mới
|
|
let headerId = req.params.id;
|
|
|
|
if (!headerId) {
|
|
// Tìm header đầu tiên
|
|
let header = await Header.findOne().sort({ order: 1 });
|
|
if (!header) {
|
|
console.log("No existing header found, creating new one");
|
|
// Tạo header mới nếu chưa có
|
|
header = new Header({
|
|
top,
|
|
offcanvas,
|
|
menu,
|
|
logo: logo ? { light: logo } : {},
|
|
ctaButton,
|
|
status: status || "active",
|
|
order: order || 1,
|
|
});
|
|
await header.save();
|
|
console.log("✓ Header created:", header._id);
|
|
return res.json({
|
|
success: true,
|
|
message: "Header created successfully",
|
|
data: header,
|
|
});
|
|
}
|
|
headerId = header._id;
|
|
console.log("✓ Found existing header:", headerId);
|
|
}
|
|
|
|
// Chuẩn bị dữ liệu logo - merge với dữ liệu cũ
|
|
let logoData = {};
|
|
if (logo) {
|
|
// Nếu có logo mới, lấy dữ liệu cũ và update light
|
|
const existingHeader = await Header.findById(headerId);
|
|
logoData = {
|
|
light: logo,
|
|
dark: existingHeader?.logo?.dark || "",
|
|
alt: existingHeader?.logo?.alt || "",
|
|
};
|
|
}
|
|
|
|
const updateData = {
|
|
top,
|
|
offcanvas,
|
|
menu,
|
|
ctaButton,
|
|
status,
|
|
order,
|
|
};
|
|
|
|
if (logo) {
|
|
updateData.logo = logoData;
|
|
}
|
|
|
|
console.log(
|
|
"Preparing to update header with data:",
|
|
JSON.stringify(updateData, null, 2),
|
|
);
|
|
|
|
// ✅ Capture BEFORE state
|
|
const beforeHeader = await Header.findById(headerId);
|
|
const beforeData = beforeHeader
|
|
? JSON.parse(JSON.stringify(beforeHeader.toObject()))
|
|
: {};
|
|
|
|
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
|
|
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 active header
|
|
exports.api = async (req, res) => {
|
|
try {
|
|
const header = await Header.findOne({ status: "active" }).sort({
|
|
order: 1,
|
|
});
|
|
|
|
if (!header) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "No active header found",
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: header,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|
|
|
|
// Public API: Get menu tree structure
|
|
exports.getMenuTreeAPI = async (req, res) => {
|
|
try {
|
|
const header = await Header.findOne({ status: "active" }).sort({
|
|
order: 1,
|
|
});
|
|
|
|
if (!header || !header.menu) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: "No active menu found",
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: header.menu,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message,
|
|
});
|
|
}
|
|
};
|