Merge branch 'develop' of https://gits.techvanguard.vn/UKSOURCE/cms.lams into feat/duy-20042026-HomepageAbout

This commit is contained in:
2026-04-22 15:40:47 +07:00
7 changed files with 361 additions and 776 deletions
+88 -151
View File
@@ -33,29 +33,23 @@ exports.index = async (req, res) => {
// 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 || "",
signInButton: header.signInButton || {
label: "Sign In",
href: "/signin",
},
ctaButton: header.ctaButton || {
label: "Request Info",
href: "/request",
},
}
: {
topbar: {
contactInfo: {
phone: "",
email: "",
location: "",
},
socialLinks: [],
},
logo: "",
signInButton: { label: "Sign In", href: "/signin" },
ctaButton: { label: "Request Info", href: "/request" },
};
const activeTab = req.query.tab || "topbar";
const activeTab = req.query.tab || "logo";
// Always fetch menu items to ensure they are available even if the user
// switches tabs client-side
@@ -123,14 +117,12 @@ exports.show = async (req, res) => {
// Admin: Create header
exports.store = async (req, res) => {
try {
const { top, offcanvas, menu, logo, ctaButton, status, order } = req.body;
const { logo, signInButton, ctaButton, status, order } = req.body;
const header = new Header({
top,
offcanvas,
menu,
logo,
ctaButton,
logo: logo ? { light: logo } : {},
signInButton: signInButton || { label: "Sign In", href: "/signin" },
ctaButton: ctaButton || {},
status: status || "active",
order: order || 1,
});
@@ -152,129 +144,81 @@ exports.store = async (req, res) => {
// Admin: Update header
exports.update = async (req, res) => {
try {
let { top, topbarJson, offcanvas, menu, logo, ctaButton, status, order } =
req.body;
const { logo, signInButton, 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 || [],
};
// Upsert logic: find existing header or prepare to create new one
let header = await Header.findOne().sort({ order: 1 });
let headerId = header?._id;
if (logo) {
updateData.logo = logoData;
}
// Capture BEFORE state for audit logging
const beforeData = header
? JSON.parse(JSON.stringify(header.toObject()))
: {};
console.log(
"Preparing to update header with data:",
JSON.stringify(updateData, null, 2),
);
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);
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,
// 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,
});
}
// 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 = {};
// Prepare logo data - merge with existing data
let logoData = header.logo || {};
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 || "",
dark: header.logo?.dark || "",
alt: header.logo?.alt || "",
};
}
// Prepare update data
const updateData = {
top,
offcanvas,
menu,
ctaButton,
status,
order,
logo: logoData,
signInButton: signInButton || header.signInButton,
ctaButton: ctaButton || header.ctaButton,
};
if (logo) {
updateData.logo = logoData;
}
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),
);
// ✅ Capture BEFORE state
const beforeHeader = await Header.findById(headerId);
const beforeData = beforeHeader
? JSON.parse(JSON.stringify(beforeHeader.toObject()))
: {};
// Update existing header
const updatedHeader = await Header.findByIdAndUpdate(headerId, updateData, {
new: true,
runValidators: true,
@@ -288,10 +232,10 @@ exports.update = async (req, res) => {
});
}
// Capture AFTER state
// Capture AFTER state for audit logging
const afterData = JSON.parse(JSON.stringify(updatedHeader.toObject()));
// ✅ AUDIT LOGGING - Header Updated
// Audit logging - Header Updated
const changes = diffObject(beforeData, afterData);
if (changes.length > 0) {
await writeAuditLog({
@@ -384,23 +328,41 @@ exports.destroy = async (req, res) => {
}
};
// Public API: Get active header
// Public API: Get header (returns header regardless of status)
exports.api = async (req, res) => {
try {
const header = await Header.findOne({ status: "active" }).sort({
const header = await Header.findOne().sort({
order: 1,
});
if (!header) {
return res.status(404).json({
success: false,
message: "No active header found",
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: header,
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({
@@ -410,28 +372,3 @@ exports.api = async (req, res) => {
}
};
// 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,
});
}
};
+2 -1
View File
@@ -21,6 +21,7 @@ const buildMenuTree = (items, parentId = null, isPublic = false) => {
title: item.title,
url: item.url,
type: item.type,
status: item.status || 'active',
};
}
@@ -196,7 +197,7 @@ exports.reorder = async (req, res) => {
// 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 items = await HeaderMenu.find().sort({ order: 1 });
const tree = buildMenuTree(items, null, true);
res.json({ success: true, data: tree });
} catch (error) {