forked from UKSOURCE/cms.hailearning.edu.vn
refactor: add auto-generated menu for new services
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const { getServiceData } = require("../services/service.service");
|
||||
const Service = require("../models/service");
|
||||
const syncServiceMenu = require("../services/syncServiceMenu");
|
||||
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
|
||||
const writeAuditLog = require("../audit/writeAuditLog");
|
||||
const diffObject = require("../audit/diffObject");
|
||||
@@ -98,6 +99,8 @@ exports.updateService = async (req, res) => {
|
||||
changes,
|
||||
req,
|
||||
});
|
||||
// Sync header menu children to reflect updated service name/slug
|
||||
await syncServiceMenu(updatedData.services?.items || []);
|
||||
req.flash("success_msg", "Service updated successfully");
|
||||
res.redirect("/admin/service");
|
||||
} catch (err) {
|
||||
@@ -168,6 +171,9 @@ exports.update = async (req, res) => {
|
||||
await Service.create(updatedData);
|
||||
}
|
||||
|
||||
// Sync header menu children to reflect current service list
|
||||
await syncServiceMenu(updatedData.services?.items || []);
|
||||
|
||||
req.flash("success_msg", "Service updated successfully");
|
||||
res.redirect("/admin/service");
|
||||
} catch (err) {
|
||||
|
||||
34
scripts/sync-service-menu-now.js
Normal file
34
scripts/sync-service-menu-now.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* One-time script: sync service menu items from DB into HeaderMenu.
|
||||
* Run: node scripts/sync-service-menu-now.js
|
||||
*/
|
||||
const mongoose = require("mongoose");
|
||||
const dotenv = require("dotenv");
|
||||
dotenv.config();
|
||||
|
||||
const Service = require("../models/service");
|
||||
const syncServiceMenu = require("../services/syncServiceMenu");
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/hailearning";
|
||||
|
||||
async function run() {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
console.log("✅ Connected to MongoDB");
|
||||
|
||||
const serviceDoc = await Service.findOne().lean();
|
||||
if (!serviceDoc?.services?.items?.length) {
|
||||
console.log("⚠️ No services found in DB.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Found ${serviceDoc.services.items.length} services. Syncing menu...`);
|
||||
await syncServiceMenu(serviceDoc.services.items);
|
||||
|
||||
console.log("✅ Done.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("❌ Error:", err);
|
||||
process.exit(1);
|
||||
});
|
||||
57
services/syncServiceMenu.js
Normal file
57
services/syncServiceMenu.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Sync HeaderMenu children of the "Services" menu item
|
||||
* to match the current list of services in the database.
|
||||
*
|
||||
* Strategy:
|
||||
* - Find the HeaderMenu item whose url === '/services'
|
||||
* - Delete all its direct children
|
||||
* - Re-create one child per service item (url = /services/<slug>)
|
||||
*/
|
||||
|
||||
const HeaderMenu = require("../models/headerMenu");
|
||||
const slugify = require("slugify");
|
||||
|
||||
/**
|
||||
* @param {Array} serviceItems - array of service objects { slug, name }
|
||||
*/
|
||||
const syncServiceMenu = async (serviceItems = []) => {
|
||||
try {
|
||||
// 1. Find the "Services" parent menu item
|
||||
const servicesParent = await HeaderMenu.findOne({ url: "/services" });
|
||||
|
||||
if (!servicesParent) {
|
||||
console.warn("[syncServiceMenu] No HeaderMenu item with url=/services found. Skipping sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
const parentId = servicesParent._id;
|
||||
|
||||
// 2. Remove all existing children of that parent
|
||||
await HeaderMenu.deleteMany({ parentId });
|
||||
|
||||
// 3. Re-create one child per service
|
||||
const ops = serviceItems
|
||||
.filter((s) => s && s.slug && s.name)
|
||||
.map((s, index) => ({
|
||||
title: s.name,
|
||||
slug: slugify(s.name, { lower: true, strict: true }),
|
||||
url: `/services/${s.slug}`,
|
||||
parentId,
|
||||
order: index + 1,
|
||||
status: "active",
|
||||
type: "internal",
|
||||
is_maintainance: false,
|
||||
}));
|
||||
|
||||
if (ops.length > 0) {
|
||||
await HeaderMenu.insertMany(ops);
|
||||
}
|
||||
|
||||
console.log(`[syncServiceMenu] Synced ${ops.length} service menu items under parentId=${parentId}`);
|
||||
} catch (err) {
|
||||
// Non-fatal – log but don't crash the main request
|
||||
console.error("[syncServiceMenu] Error syncing service menu:", err.message);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = syncServiceMenu;
|
||||
@@ -2,6 +2,14 @@
|
||||
<div class="tab-pane fade" id="floatingcontact" role="tabpanel">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm mb-1">
|
||||
<div class="card-header bg-white d-flex justify-content-center align-items-center">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="floatingContactEnabled"
|
||||
<%=(data.floatingContact?.enabled !== false ) ? 'checked' : '' %>>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
@@ -11,21 +19,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-4 align-items-start">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 p-3 border rounded-3 bg-light-subtle">
|
||||
<div>
|
||||
<div class="fw-semibold">Widget visibility</div>
|
||||
<small class="text-muted">Enable or disable the floating contact widget on the homepage.</small>
|
||||
</div>
|
||||
<div class="form-check form-switch m-0">
|
||||
<input class="form-check-input" type="checkbox" id="floatingContactEnabled"
|
||||
<%= data.floatingContact?.enabled !== false ? 'checked' : '' %> />
|
||||
<label class="form-check-label fw-medium" for="floatingContactEnabled">
|
||||
Enable floating widget
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<label class="form-label fw-medium">Panel Title</label>
|
||||
<input type="text" class="form-control" id="floatingContactPanelTitle"
|
||||
|
||||
Reference in New Issue
Block a user