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 { getServiceData } = require("../services/service.service");
|
||||||
const Service = require("../models/service");
|
const Service = require("../models/service");
|
||||||
|
const syncServiceMenu = require("../services/syncServiceMenu");
|
||||||
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
|
const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper");
|
||||||
const writeAuditLog = require("../audit/writeAuditLog");
|
const writeAuditLog = require("../audit/writeAuditLog");
|
||||||
const diffObject = require("../audit/diffObject");
|
const diffObject = require("../audit/diffObject");
|
||||||
@@ -98,6 +99,8 @@ exports.updateService = async (req, res) => {
|
|||||||
changes,
|
changes,
|
||||||
req,
|
req,
|
||||||
});
|
});
|
||||||
|
// Sync header menu children to reflect updated service name/slug
|
||||||
|
await syncServiceMenu(updatedData.services?.items || []);
|
||||||
req.flash("success_msg", "Service updated successfully");
|
req.flash("success_msg", "Service updated successfully");
|
||||||
res.redirect("/admin/service");
|
res.redirect("/admin/service");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -168,6 +171,9 @@ exports.update = async (req, res) => {
|
|||||||
await Service.create(updatedData);
|
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");
|
req.flash("success_msg", "Service updated successfully");
|
||||||
res.redirect("/admin/service");
|
res.redirect("/admin/service");
|
||||||
} catch (err) {
|
} 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="tab-pane fade" id="floatingcontact" role="tabpanel">
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
<div class="col-md-12">
|
<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 border shadow-sm">
|
||||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||||
<h6 class="mb-0">
|
<h6 class="mb-0">
|
||||||
@@ -11,21 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row g-4 align-items-start">
|
<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">
|
<div class="col-lg-8">
|
||||||
<label class="form-label fw-medium">Panel Title</label>
|
<label class="form-label fw-medium">Panel Title</label>
|
||||||
<input type="text" class="form-control" id="floatingContactPanelTitle"
|
<input type="text" class="form-control" id="floatingContactPanelTitle"
|
||||||
|
|||||||
Reference in New Issue
Block a user