forked from UKSOURCE/cms.lams
Fix: about feature
This commit is contained in:
@@ -1,251 +0,0 @@
|
||||
const { addBaseUrlToImages } = require("../utils/imageHelper");
|
||||
const AboutUs = require("../models/aboutUs");
|
||||
const Blog = require("../models/blog");
|
||||
const jsonHelper = require("../utils/jsonHelper");
|
||||
const writeAuditLog = require("../audit/writeAuditLog");
|
||||
const diffObject = require("../audit/diffObject");
|
||||
const AUDIT_ACTIONS = require("../constants/auditAction");
|
||||
|
||||
/**
|
||||
* GET /api/about
|
||||
* Lấy dữ liệu About Us (Public API cho website và CMS load dữ liệu)
|
||||
*/
|
||||
exports.getAbout = async (req, res) => {
|
||||
try {
|
||||
// Force no-cache headers
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Expires", "0");
|
||||
|
||||
const data = await AboutUs.getSingle();
|
||||
const rawData = data.toObject();
|
||||
|
||||
// === Dynamic Blog News Section ===
|
||||
const news = rawData.news || {};
|
||||
let blogs = [];
|
||||
|
||||
// Nếu có chọn blog cụ thể
|
||||
if (news.selectedBlogIds && news.selectedBlogIds.length > 0) {
|
||||
blogs = await Blog.find({
|
||||
_id: { $in: news.selectedBlogIds },
|
||||
status: "published",
|
||||
}).lean();
|
||||
|
||||
// Sắp xếp theo thứ tự đã chọn trong selectedBlogIds
|
||||
blogs.sort((a, b) => {
|
||||
return (
|
||||
news.selectedBlogIds.indexOf(a._id.toString()) -
|
||||
news.selectedBlogIds.indexOf(b._id.toString())
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Nếu không chọn hoặc chọn nhưng không đủ, lấy thêm 3 bài mới nhất
|
||||
if (blogs.length === 0) {
|
||||
blogs = await Blog.find({ status: "published" })
|
||||
.sort({ createdAt: -1 })
|
||||
.limit(3)
|
||||
.lean();
|
||||
}
|
||||
|
||||
// Map dữ liệu blog sang format mà frontend mong đợi
|
||||
news.items = blogs.map((blog) => ({
|
||||
title: blog.title,
|
||||
category: blog.category && blog.category[0] ? blog.category[0] : "Visa",
|
||||
date:
|
||||
blog.publishedAt ||
|
||||
new Date(blog.createdAt).toLocaleDateString("en-GB", {
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
}),
|
||||
comments: blog.commentsCount || 0,
|
||||
author: {
|
||||
name: blog.author || "Admin",
|
||||
avatar: "/assets/img/home-1/news/client.png", // Default avatar
|
||||
},
|
||||
link: `/blog/${blog.slug}`,
|
||||
thumbnail: blog.featuredImage,
|
||||
}));
|
||||
|
||||
rawData.news = news;
|
||||
// ===============================
|
||||
|
||||
const baseUrl =
|
||||
process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`;
|
||||
const processedData = addBaseUrlToImages(rawData, baseUrl);
|
||||
|
||||
res.json(processedData);
|
||||
} catch (error) {
|
||||
console.error("Error getting about data:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to get about data",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PUT /api/about
|
||||
* Cập nhật dữ liệu About Us (Dùng cho AJAX từ CMS)
|
||||
*/
|
||||
exports.updateAbout = async (req, res) => {
|
||||
try {
|
||||
let updateData = req.body;
|
||||
|
||||
// Nếu dữ liệu gửi qua trường aboutJson (dạng string JSON)
|
||||
if (updateData.aboutJson && typeof updateData.aboutJson === "string") {
|
||||
try {
|
||||
updateData = JSON.parse(updateData.aboutJson);
|
||||
} catch (e) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Invalid JSON in aboutJson",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const doc = await AboutUs.getSingle();
|
||||
|
||||
// ✅ Capture BEFORE state
|
||||
const beforeData = JSON.parse(JSON.stringify(doc.toObject()));
|
||||
|
||||
// Use .set() for better handling of nested objects/arrays in Mongoose
|
||||
doc.set(updateData);
|
||||
await doc.save();
|
||||
|
||||
// ✅ Capture AFTER state
|
||||
const afterData = JSON.parse(JSON.stringify(doc.toObject()));
|
||||
|
||||
// ✅ AUDIT LOGGING - About Us Updated
|
||||
const changes = diffObject(beforeData, afterData);
|
||||
if (changes.length > 0) {
|
||||
await writeAuditLog({
|
||||
model: "AboutUs",
|
||||
documentId: doc._id,
|
||||
action: AUDIT_ACTIONS.UPDATE_ABOUT_US,
|
||||
before: beforeData,
|
||||
after: afterData,
|
||||
changes,
|
||||
req,
|
||||
});
|
||||
console.log(
|
||||
`✅ Audit log created for About Us update: ${changes.length} changes`,
|
||||
);
|
||||
} else {
|
||||
console.log("ℹ️ No changes detected for About Us update");
|
||||
}
|
||||
|
||||
// Fetch fresh data for syncing and returning
|
||||
const finalData = await AboutUs.findOne()
|
||||
.select("-_id -__v -createdAt -updatedAt")
|
||||
.lean();
|
||||
|
||||
// Update about.json file to keep it in sync
|
||||
jsonHelper.writeJsonFile("about", finalData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "About Us updated successfully",
|
||||
data: finalData,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating about data:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: "Failed to update about data: " + error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render admin page (Dùng cho Admin UI)
|
||||
*/
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const data = await AboutUs.getSingle();
|
||||
const rawData = data.toObject();
|
||||
|
||||
// Lấy tất cả blog để chọn trong CMS
|
||||
const allBlogs = await Blog.find({ status: "published" })
|
||||
.sort({ createdAt: -1 })
|
||||
.lean();
|
||||
|
||||
const activeTab = req.query.activeTab || "hero";
|
||||
res.render("admin/aboutUs/index", {
|
||||
layout: "layouts/main",
|
||||
title: "About Us Management",
|
||||
data: rawData,
|
||||
allBlogs,
|
||||
activeTab,
|
||||
user: req.session.user,
|
||||
currentPath: req.path,
|
||||
frontendUrl: process.env.FRONTEND_URL || "http://localhost:3000",
|
||||
backendUrl: process.env.BACKEND_URL || "http://localhost:3001",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Error in about index:", err);
|
||||
req.flash("error_msg", "Error loading About Us page");
|
||||
res.redirect("/admin/dashboard");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update method cho form-based submission (Admin UI - Post fallback)
|
||||
*/
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
let updateData = req.body;
|
||||
if (updateData.aboutJson && typeof updateData.aboutJson === "string") {
|
||||
try {
|
||||
updateData = JSON.parse(updateData.aboutJson);
|
||||
} catch (e) {
|
||||
req.flash("error_msg", "Invalid JSON data");
|
||||
return res.redirect("/admin/about-us");
|
||||
}
|
||||
}
|
||||
|
||||
const doc = await AboutUs.getSingle();
|
||||
|
||||
// ✅ Capture BEFORE state
|
||||
const beforeData = JSON.parse(JSON.stringify(doc.toObject()));
|
||||
|
||||
doc.set(updateData);
|
||||
await doc.save();
|
||||
|
||||
// ✅ Capture AFTER state
|
||||
const afterData = JSON.parse(JSON.stringify(doc.toObject()));
|
||||
|
||||
// ✅ AUDIT LOGGING - About Us Updated
|
||||
const changes = diffObject(beforeData, afterData);
|
||||
if (changes.length > 0) {
|
||||
await writeAuditLog({
|
||||
model: "AboutUs",
|
||||
documentId: doc._id,
|
||||
action: AUDIT_ACTIONS.UPDATE_ABOUT_US,
|
||||
before: beforeData,
|
||||
after: afterData,
|
||||
changes,
|
||||
req,
|
||||
});
|
||||
}
|
||||
|
||||
const finalData = await AboutUs.findOne()
|
||||
.select("-_id -__v -createdAt -updatedAt")
|
||||
.lean();
|
||||
jsonHelper.writeJsonFile("about", finalData);
|
||||
|
||||
req.flash("success_msg", "About Us updated successfully");
|
||||
const activeTab = req.query.activeTab || "hero";
|
||||
res.redirect(`/admin/about-us?activeTab=${activeTab}`);
|
||||
} catch (err) {
|
||||
console.error("Update error:", err);
|
||||
req.flash("error_msg", "Error updating About Us: " + err.message);
|
||||
res.redirect("/admin/about-us");
|
||||
}
|
||||
};
|
||||
|
||||
// Aliases for compatibility
|
||||
exports.api = exports.getAbout;
|
||||
exports.page = exports.getAbout;
|
||||
exports.updateAboutUs = exports.updateAbout;
|
||||
+4
-4
@@ -37,7 +37,7 @@
|
||||
"categoryLabel": "Student Experience",
|
||||
"title": "Innovation in Student Experience",
|
||||
"description": "Enhanced student support through integrated digital services, academic advising, and career development platforms.",
|
||||
"image": "/uploads/history/Colorful_Square_Background.png",
|
||||
"image": "/uploads/history/2026.png",
|
||||
"imageAlt": "",
|
||||
"stats": [],
|
||||
"featured": true
|
||||
@@ -50,7 +50,7 @@
|
||||
"categoryLabel": "Global",
|
||||
"title": "Expansion of International Partnerships",
|
||||
"description": "Established collaborations with academic institutions and industry partners across regions, enabling dual qualifications and cross-border learning opportunities.",
|
||||
"image": "/uploads/history/7281.jpg",
|
||||
"image": "/uploads/history/2025.png",
|
||||
"imageAlt": "",
|
||||
"stats": [],
|
||||
"featured": false
|
||||
@@ -95,7 +95,7 @@
|
||||
"featured": false
|
||||
},
|
||||
{
|
||||
"id": "strategic-academic-framework",
|
||||
"id": "strategic-academic-framework-2",
|
||||
"year": "2027",
|
||||
"yearRange": "2020 - Present",
|
||||
"category": "All Categories",
|
||||
@@ -109,4 +109,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"hero": {
|
||||
"badge": "Global NetworkGlobal NetworkGlobal Netwo",
|
||||
"badge": "Global Network",
|
||||
"title": "Industry & Academic Partnerships.Industry & Academic Partnerships.Industry & Academic Part",
|
||||
"description": "Global NetworkGlobal NetworkGlobal NetwoGlobal NetworkGlobal NetworkGlobal NetwoGlobal NetworkGlobal NetworkGlobal NetwoGlobal NetworkGlobal NetworkGlobal NetwoGlobal NetworkGlobal NetworkGlobal NetwoGlobal NetworkGlobal",
|
||||
"description": "Global NetworkGlobal ",
|
||||
"linkLabel": "Explore DirectoryExplore DirectoryExplor",
|
||||
"image": "/uploads/partnerships/kVI17_2B.webp",
|
||||
"imageAlt": "Modern university campus and corporate office buildingModern university campus and corporate office buildingModern unive"
|
||||
@@ -42,7 +42,7 @@
|
||||
"benefits": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryInIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndust"
|
||||
},
|
||||
{
|
||||
"id": "IndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"id": "industryindustryindustryindustryindustryindustryindustryindustryindustryindustryindustryin-2",
|
||||
"name": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"category": "IndustryIndustryIndustryIndust",
|
||||
"summary": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
@@ -53,7 +53,7 @@
|
||||
"benefits": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryInIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndust"
|
||||
},
|
||||
{
|
||||
"id": "IndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"id": "industryindustryindustryindustryindustryindustryindustryindustryindustryindustryindustryin-3",
|
||||
"name": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"category": "IndustryIndustryIndustryIndust",
|
||||
"summary": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
@@ -64,7 +64,7 @@
|
||||
"benefits": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryInIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndust"
|
||||
},
|
||||
{
|
||||
"id": "IndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"id": "industryindustryindustryindustryindustryindustryindustryindustryindustryindustryindustryin-4",
|
||||
"name": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"category": "IndustryIndustryIndustryIndust",
|
||||
"summary": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
@@ -75,7 +75,7 @@
|
||||
"benefits": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryInIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndust"
|
||||
},
|
||||
{
|
||||
"id": "IndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"id": "industryindustryindustryindustryindustryindustryindustryindustryindustryindustryindustryin-5",
|
||||
"name": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
"category": "IndustryIndustryIndustryIndust",
|
||||
"summary": "IndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIndustryIn",
|
||||
@@ -147,4 +147,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-18
@@ -1,7 +1,5 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const { Schema } = mongoose;
|
||||
|
||||
// Hero
|
||||
const HeroSchema = new Schema(
|
||||
{
|
||||
@@ -15,7 +13,6 @@ const HeroSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// Leadership
|
||||
const SocialSchema = new Schema(
|
||||
{
|
||||
@@ -24,7 +21,6 @@ const SocialSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const LeadershipMemberSchema = new Schema(
|
||||
{
|
||||
name: { type: String, default: "" },
|
||||
@@ -35,7 +31,6 @@ const LeadershipMemberSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const LeadershipSchema = new Schema(
|
||||
{
|
||||
heading: { type: String, default: "" },
|
||||
@@ -44,7 +39,6 @@ const LeadershipSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// Learning Model
|
||||
const LearningFeatureSchema = new Schema(
|
||||
{
|
||||
@@ -54,7 +48,6 @@ const LearningFeatureSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const LearningModeSchema = new Schema(
|
||||
{
|
||||
title: { type: String, default: "" },
|
||||
@@ -63,7 +56,6 @@ const LearningModeSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const LearningModelSchema = new Schema(
|
||||
{
|
||||
heading: { type: String, default: "" },
|
||||
@@ -73,7 +65,6 @@ const LearningModelSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// Accreditation
|
||||
const AccreditationBadgeSchema = new Schema(
|
||||
{
|
||||
@@ -83,7 +74,6 @@ const AccreditationBadgeSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const AccreditationStatSchema = new Schema(
|
||||
{
|
||||
value: { type: String, default: "" },
|
||||
@@ -92,7 +82,6 @@ const AccreditationStatSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const AccreditationSchema = new Schema(
|
||||
{
|
||||
heading: { type: String, default: "" },
|
||||
@@ -102,7 +91,6 @@ const AccreditationSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// Success Stories
|
||||
const StorySchema = new Schema(
|
||||
{
|
||||
@@ -113,7 +101,6 @@ const StorySchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const SuccessStoriesSchema = new Schema(
|
||||
{
|
||||
heading: { type: String, default: "" },
|
||||
@@ -122,7 +109,6 @@ const SuccessStoriesSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// CTA
|
||||
const CtaButtonSchema = new Schema(
|
||||
{
|
||||
@@ -131,7 +117,6 @@ const CtaButtonSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const CtaSchema = new Schema(
|
||||
{
|
||||
heading: { type: String, default: "" },
|
||||
@@ -141,7 +126,6 @@ const CtaSchema = new Schema(
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
// Root schema
|
||||
const AboutSchema = new Schema(
|
||||
{
|
||||
@@ -157,5 +141,4 @@ const AboutSchema = new Schema(
|
||||
strict: false,
|
||||
},
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("About", AboutSchema);
|
||||
module.exports = mongoose.model("About", AboutSchema);
|
||||
@@ -1,108 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const aboutUsSchema = new mongoose.Schema(
|
||||
{
|
||||
hero: {
|
||||
title: String,
|
||||
breadcrumb: [String],
|
||||
backgroundImage: String,
|
||||
},
|
||||
intro: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
image: String,
|
||||
},
|
||||
mission: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
images: {
|
||||
main: String,
|
||||
secondary: String,
|
||||
bgShape: String,
|
||||
planeShape: String,
|
||||
topShape: String,
|
||||
globeShape: String,
|
||||
},
|
||||
items: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
icon: String,
|
||||
label: String,
|
||||
description: String,
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
],
|
||||
features: [String],
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
},
|
||||
},
|
||||
features: {
|
||||
backgroundImage: String,
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
image: String,
|
||||
items: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
icon: String,
|
||||
title: String,
|
||||
description: String,
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
],
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
},
|
||||
},
|
||||
news: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
},
|
||||
selectedBlogIds: [{ type: mongoose.Schema.Types.ObjectId, ref: "Blog" }],
|
||||
// Deprecated: items field kept for backward compatibility during migration
|
||||
items: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
title: String,
|
||||
category: String,
|
||||
date: String,
|
||||
comments: Number,
|
||||
author: {
|
||||
name: String,
|
||||
avatar: String,
|
||||
},
|
||||
link: String,
|
||||
thumbnail: String,
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
collection: "aboutus",
|
||||
},
|
||||
);
|
||||
|
||||
// Static method để đảm bảo luôn chỉ có 1 bản ghi duy nhất (Singleton)
|
||||
aboutUsSchema.statics.getSingle = async function () {
|
||||
let doc = await this.findOne();
|
||||
if (!doc) {
|
||||
doc = await this.create({});
|
||||
}
|
||||
return doc;
|
||||
};
|
||||
|
||||
module.exports = mongoose.model("AboutUs", aboutUsSchema);
|
||||
+5
-4
@@ -7,7 +7,7 @@ const uploadController = require("../controllers/uploadController");
|
||||
const homeController = require("../controllers/homeController");
|
||||
const headerController = require("../controllers/headerController");
|
||||
const footerController = require("../controllers/footerController");
|
||||
const aboutUsController = require("../controllers/aboutUsController");
|
||||
const aboutController = require("../controllers/aboutController");
|
||||
const partnershipsController = require("../controllers/partnershipsController");
|
||||
const historyPageController = require("../controllers/historyPageController");
|
||||
const accreditationController = require("../controllers/accreditationController");
|
||||
@@ -54,9 +54,10 @@ router.param("code", (req, res, next, code) => {
|
||||
next();
|
||||
});
|
||||
|
||||
// About Us
|
||||
router.get("/about-us", ensureAuthenticated, aboutUsController.index);
|
||||
router.post("/about-us/update", ensureAuthenticated, aboutUsController.update);
|
||||
// About
|
||||
router.get("/about", ensureAuthenticated, aboutController.index);
|
||||
router.post("/about/update", ensureAuthenticated, aboutController.update);
|
||||
|
||||
router.get("/partnerships", ensureAuthenticated, partnershipsController.index);
|
||||
router.post(
|
||||
"/partnerships/update",
|
||||
|
||||
+3
-7
@@ -2,7 +2,7 @@ const express = require("express");
|
||||
const path = require("path");
|
||||
const router = express.Router();
|
||||
const homeController = require("../controllers/homeController");
|
||||
const aboutUsController = require("../controllers/aboutUsController");
|
||||
const aboutController = require("../controllers/aboutController");
|
||||
const partnershipsController = require("../controllers/partnershipsController");
|
||||
const historyPageController = require("../controllers/historyPageController");
|
||||
const accreditationController = require("../controllers/accreditationController");
|
||||
@@ -43,18 +43,14 @@ router.get("/", (req, res) => {
|
||||
router.get("/api/home", homeController.api);
|
||||
|
||||
// API để lấy dữ liệu about
|
||||
router.get("/api/about", aboutUsController.getAbout);
|
||||
router.put("/api/about", aboutUsController.updateAbout);
|
||||
router.get("/api/about", aboutController.api);
|
||||
|
||||
router.get("/api/partnerships", partnershipsController.api);
|
||||
router.get("/api/history", historyPageController.api);
|
||||
router.get("/api/accreditation", accreditationController.api);
|
||||
router.get("/api/admissions", admissionsController.api);
|
||||
router.get("/api/policies", policiesController.api);
|
||||
|
||||
// Public about-us page and API (legacy support)
|
||||
router.get("/about-us", aboutUsController.getAbout);
|
||||
router.get("/api/about-us", aboutUsController.getAbout);
|
||||
|
||||
// Header API route
|
||||
router.get("/api/header", headerController.api);
|
||||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
/**
|
||||
* Tạo migration file mới với format giống Laravel
|
||||
* Format: YYYY_MM_DD_HHMMSS_migration_name.js
|
||||
*/
|
||||
function makeMigration(migrationName) {
|
||||
if (!migrationName) {
|
||||
console.error('Error: Migration name is required');
|
||||
console.log('\nUsage: node scripts/make-migration.js <migration-name>');
|
||||
console.log('Example: node scripts/make-migration.js create_users_table');
|
||||
process.exit(1);
|
||||
}
|
||||
// Tạo timestamp theo format Laravel: YYYY_MM_DD_HHMMSS
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
const timestamp = `${year}_${month}_${day}_${hours}${minutes}${seconds}`;
|
||||
const fileName = `${timestamp}_${migrationName}.js`;
|
||||
const filePath = path.join(__dirname, fileName);
|
||||
// Template migration mẫu
|
||||
const template = `require('dotenv').config();
|
||||
const connectDB = require('../config/database');
|
||||
/**
|
||||
* Migration: ${migrationName}
|
||||
* Created: ${now.toLocaleString('vi-VN')}
|
||||
*/
|
||||
async function migrate() {
|
||||
try {
|
||||
// Kết nối database
|
||||
await connectDB();
|
||||
console.log('Starting migration: ${migrationName}...');
|
||||
|
||||
// TODO: Thêm code migration của bạn ở đây
|
||||
|
||||
console.log('Migration ${migrationName} completed successfully!');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
await mongoose.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('Migration error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Chạy migration nếu được gọi trực tiếp
|
||||
if (require.main === module) {
|
||||
migrate();
|
||||
}
|
||||
module.exports = { migrate };
|
||||
`;
|
||||
// Kiểm tra file đã tồn tại chưa
|
||||
if (fs.existsSync(filePath)) {
|
||||
console.error(`Error: Migration file already exists: ${fileName}`);
|
||||
process.exit(1);
|
||||
}
|
||||
// Tạo file migration
|
||||
try {
|
||||
fs.writeFileSync(filePath, template, 'utf8');
|
||||
console.log(`Migration created successfully: ${fileName}`);
|
||||
console.log(`Path: ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error('Error creating migration file:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Lấy migration name từ command line arguments
|
||||
const migrationName = process.argv[2];
|
||||
// Chạy hàm tạo migration
|
||||
makeMigration(migrationName);
|
||||
@@ -10,12 +10,8 @@ async function validateAboutData(data) {
|
||||
}
|
||||
|
||||
// Tuỳ schema bạn chỉnh lại cho chuẩn hơn
|
||||
if (!data.title || !data.sections) {
|
||||
throw new Error('Missing required fields: title or sections');
|
||||
}
|
||||
|
||||
if (!Array.isArray(data.sections)) {
|
||||
throw new Error('sections must be an array');
|
||||
if (!data.hero || !data.leadership) {
|
||||
throw new Error('Missing required fields: hero or leadership');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,11 +20,9 @@ async function migrateAboutData() {
|
||||
await connectDB();
|
||||
console.log('Đã kết nối MongoDB...');
|
||||
|
||||
// Xóa dữ liệu cũ
|
||||
await About.deleteMany({});
|
||||
console.log('Đã xóa dữ liệu About cũ');
|
||||
|
||||
// Đọc file JSON
|
||||
const aboutData = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(__dirname, '../data/about.json'),
|
||||
@@ -38,13 +32,11 @@ async function migrateAboutData() {
|
||||
|
||||
// Validate
|
||||
await validateAboutData(aboutData);
|
||||
|
||||
// Transform (optional)
|
||||
const finalData = {
|
||||
...aboutData,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
// Insert
|
||||
await About.create(finalData);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require("dotenv").config();
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const {execSync} = require("child_process");
|
||||
const { execSync } = require("child_process");
|
||||
const connectDB = require("../config/database");
|
||||
const migrationHelper = require("../utils/migrationHelper");
|
||||
|
||||
@@ -16,9 +16,10 @@ function discoverMigrations() {
|
||||
// Danh sách các file quản lý migration cần loại trừ
|
||||
const excludeFiles = [
|
||||
"migrate-all.js",
|
||||
"migrate-home.js",
|
||||
"migrate-about.js",
|
||||
|
||||
"migrate-status.js",
|
||||
"migrate-rollback.js",
|
||||
"migrate-fresh.js",
|
||||
"make-migration.js",
|
||||
];
|
||||
|
||||
const migrations = files
|
||||
@@ -77,7 +78,7 @@ async function runMigrationScript(migration) {
|
||||
async function runAllMigrations() {
|
||||
const mongoose = require("mongoose");
|
||||
let ownConn = false;
|
||||
|
||||
|
||||
try {
|
||||
const wasConnected = mongoose.connection.readyState === 1;
|
||||
await connectDB();
|
||||
@@ -95,7 +96,7 @@ async function runAllMigrations() {
|
||||
const hasRun = await migrationHelper.hasRun(migration.name);
|
||||
|
||||
if (hasRun) {
|
||||
results.push({name: migration.name, status: "SKIPPED"});
|
||||
results.push({ name: migration.name, status: "SKIPPED" });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ async function runAllMigrations() {
|
||||
}
|
||||
await migrationHelper.markAsRun(migration.name, batch);
|
||||
|
||||
results.push({name: migration.name, status: "DONE"});
|
||||
results.push({ name: migration.name, status: "DONE" });
|
||||
} catch (error) {
|
||||
results.push({
|
||||
name: migration.name,
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
require('dotenv').config();
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { execSync } = require("child_process");
|
||||
const connectDB = require("../config/database");
|
||||
const migrationHelper = require("../utils/migrationHelper");
|
||||
/**
|
||||
* Tự động phát hiện tất cả các file script trong thư mục scripts
|
||||
* Loại trừ các file quản lý migration và file không phải .js
|
||||
*/
|
||||
function discoverMigrations() {
|
||||
const scriptsDir = __dirname;
|
||||
const files = fs.readdirSync(scriptsDir);
|
||||
|
||||
// Danh sách các file quản lý migration cần loại trừ
|
||||
const excludeFiles = [
|
||||
'migrate-all.js',
|
||||
'migrate-status.js',
|
||||
'migrate-rollback.js',
|
||||
'migrate-fresh.js',
|
||||
'make-migration.js',
|
||||
'MIGRATION_README.md'
|
||||
];
|
||||
|
||||
const migrations = files
|
||||
.filter(file => {
|
||||
// Lấy tất cả file .js, trừ các file quản lý
|
||||
return file.endsWith('.js') && !excludeFiles.includes(file);
|
||||
})
|
||||
.map(file => {
|
||||
// Tạo tên migration từ tên file (bỏ .js)
|
||||
const name = file.replace('.js', '');
|
||||
return {
|
||||
name: name,
|
||||
script: file
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
// Sắp xếp theo tên để đảm bảo thứ tự nhất quán
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return migrations;
|
||||
}
|
||||
/**
|
||||
* Chạy migration script (suppress output)
|
||||
* Sử dụng child_process để chạy script độc lập vì các script tự quản lý DB connection
|
||||
* Output từ script sẽ bị suppress để chỉ hiển thị status
|
||||
*/
|
||||
async function runMigrationScript(migration) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// Chạy script bằng child_process với stdio: 'pipe' để suppress output
|
||||
// Nhưng vẫn capture stderr để có thể hiển thị lỗi nếu cần
|
||||
const result = execSync(`node scripts/${migration.script}`, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'], // stdin: ignore, stdout: pipe, stderr: pipe
|
||||
cwd: path.join(__dirname, ".."),
|
||||
encoding: 'utf8'
|
||||
});
|
||||
resolve();
|
||||
} catch (error) {
|
||||
// Attach stderr vào error để có thể hiển thị sau
|
||||
if (error.stderr) {
|
||||
error.stderr = error.stderr;
|
||||
}
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Hiển thị bảng kết quả migration đơn giản
|
||||
*/
|
||||
function displayResults(results) {
|
||||
console.log("\nRunning migrations...\n");
|
||||
|
||||
// Tìm độ dài tên migration dài nhất để format bảng
|
||||
const maxNameLength = Math.max(...results.map(r => r.name.length), 20);
|
||||
const statusWidth = 10;
|
||||
const totalWidth = maxNameLength + statusWidth + 7; // 7 = spaces and separators
|
||||
|
||||
// Header
|
||||
console.log("=".repeat(totalWidth));
|
||||
console.log(`${'Migration'.padEnd(maxNameLength)} | ${'Status'.padEnd(statusWidth)}`);
|
||||
console.log("=".repeat(totalWidth));
|
||||
|
||||
// Rows
|
||||
results.forEach(result => {
|
||||
let statusText = "";
|
||||
if (result.status === 'DONE') {
|
||||
statusText = "DONE".padEnd(statusWidth);
|
||||
} else if (result.status === 'FAIL') {
|
||||
statusText = "FAIL".padEnd(statusWidth);
|
||||
}
|
||||
|
||||
console.log(`${result.name.padEnd(maxNameLength)} | ${statusText}`);
|
||||
});
|
||||
|
||||
// Footer
|
||||
console.log("=".repeat(totalWidth));
|
||||
|
||||
// Summary
|
||||
const doneCount = results.filter(r => r.status === 'DONE').length;
|
||||
const failCount = results.filter(r => r.status === 'FAIL').length;
|
||||
|
||||
console.log("");
|
||||
if (doneCount > 0) {
|
||||
console.log(`${doneCount} migration(s) completed`);
|
||||
}
|
||||
if (failCount > 0) {
|
||||
console.log(`${failCount} migration(s) failed`);
|
||||
}
|
||||
console.log("");
|
||||
}
|
||||
/**
|
||||
* Hàm chính để chạy lại tất cả migrations từ đầu (fresh)
|
||||
* Xóa tất cả tracking và chạy lại từ đầu
|
||||
*/
|
||||
async function runFreshMigrations() {
|
||||
const mongoose = require('mongoose');
|
||||
let ownConn = false;
|
||||
|
||||
try {
|
||||
const wasConnected = mongoose.connection.readyState === 1;
|
||||
await connectDB();
|
||||
if (!wasConnected) ownConn = true;
|
||||
// Tự động phát hiện migrations
|
||||
const migrations = discoverMigrations();
|
||||
// Xóa tất cả tracking migrations
|
||||
const Migration = require('../models/migration');
|
||||
await Migration.deleteMany({});
|
||||
const batch = 1; // Batch mới bắt đầu từ 1
|
||||
const results = [];
|
||||
// Chạy từng migration
|
||||
for (let i = 0; i < migrations.length; i++) {
|
||||
const migration = migrations[i];
|
||||
|
||||
try {
|
||||
// Chạy migration script (output bị suppress)
|
||||
await runMigrationScript(migration);
|
||||
|
||||
if (mongoose.connection.readyState !== 1) {
|
||||
await connectDB();
|
||||
}
|
||||
await migrationHelper.markAsRun(migration.name, batch);
|
||||
|
||||
results.push({ name: migration.name, status: 'DONE' });
|
||||
} catch (error) {
|
||||
results.push({ name: migration.name, status: 'FAIL', error: error.message });
|
||||
// Hiển thị bảng kết quả trước khi exit
|
||||
displayResults(results);
|
||||
console.error(`\n❌ Migration "${migration.name}" failed: ${error.message}`);
|
||||
if (error.stderr) {
|
||||
console.error(error.stderr.toString());
|
||||
}
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Hiển thị bảng kết quả
|
||||
displayResults(results);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("\n❌ Error:", error.message);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Chạy hàm chính
|
||||
runFreshMigrations();
|
||||
@@ -0,0 +1,105 @@
|
||||
require('dotenv').config();
|
||||
const connectDB = require('../config/database');
|
||||
const migrationHelper = require('../utils/migrationHelper');
|
||||
/**
|
||||
* Rollback một migration cụ thể
|
||||
*/
|
||||
async function rollbackMigration(migrationName) {
|
||||
const mongoose = require('mongoose');
|
||||
let ownConn = false;
|
||||
|
||||
try {
|
||||
const wasConnected = mongoose.connection.readyState === 1;
|
||||
await connectDB();
|
||||
if (!wasConnected) ownConn = true;
|
||||
|
||||
const hasRun = await migrationHelper.hasRun(migrationName);
|
||||
if (!hasRun) {
|
||||
console.log(`⚠️ Migration "${migrationName}" chưa được chạy, không thể rollback.`);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const result = await migrationHelper.rollback(migrationName);
|
||||
if (result) {
|
||||
console.log(`✅ Đã rollback migration: ${migrationName}`);
|
||||
} else {
|
||||
console.log(`❌ Không thể rollback migration: ${migrationName}`);
|
||||
}
|
||||
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Lỗi:', error.message);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Rollback batch cuối cùng
|
||||
*/
|
||||
async function rollbackLastBatch() {
|
||||
const mongoose = require('mongoose');
|
||||
let ownConn = false;
|
||||
|
||||
try {
|
||||
const wasConnected = mongoose.connection.readyState === 1;
|
||||
await connectDB();
|
||||
if (!wasConnected) ownConn = true;
|
||||
|
||||
const lastBatch = await migrationHelper.getLastBatch();
|
||||
if (lastBatch === 0) {
|
||||
console.log('⚠️ Không có batch nào để rollback.');
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const migrations = await migrationHelper.getMigrationsByBatch(lastBatch);
|
||||
if (migrations.length === 0) {
|
||||
console.log(`⚠️ Batch ${lastBatch} không có migration nào.`);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`\n🔄 Đang rollback batch ${lastBatch}...`);
|
||||
console.log(`📋 Các migration sẽ được rollback:`);
|
||||
migrations.forEach(m => {
|
||||
console.log(` - ${m.name}`);
|
||||
});
|
||||
|
||||
const deletedCount = await migrationHelper.rollbackBatch(lastBatch);
|
||||
console.log(`\n✅ Đã rollback ${deletedCount} migration(s) trong batch ${lastBatch}`);
|
||||
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Lỗi:', error.message);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Xử lý command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.log('Usage:');
|
||||
console.log(' node scripts/migrate-rollback.js <migration-name> - Rollback một migration cụ thể');
|
||||
console.log(' node scripts/migrate-rollback.js --batch - Rollback batch cuối cùng');
|
||||
process.exit(0);
|
||||
}
|
||||
if (args[0] === '--batch') {
|
||||
rollbackLastBatch();
|
||||
} else {
|
||||
rollbackMigration(args[0]);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
require('dotenv').config();
|
||||
const connectDB = require('../config/database');
|
||||
const migrationHelper = require('../utils/migrationHelper');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
/**
|
||||
* Tự động phát hiện tất cả các file script trong thư mục scripts
|
||||
* Loại trừ các file quản lý migration và file không phải .js
|
||||
*/
|
||||
function discoverMigrations() {
|
||||
const scriptsDir = __dirname;
|
||||
const files = fs.readdirSync(scriptsDir);
|
||||
|
||||
// Danh sách các file quản lý migration cần loại trừ
|
||||
const excludeFiles = [
|
||||
'migrate-all.js',
|
||||
'migrate-status.js',
|
||||
'migrate-rollback.js',
|
||||
'migrate-fresh.js',
|
||||
'make-migration.js',
|
||||
'MIGRATION_README.md'
|
||||
];
|
||||
|
||||
const migrations = files
|
||||
.filter(file => {
|
||||
// Lấy tất cả file .js, trừ các file quản lý
|
||||
return file.endsWith('.js') && !excludeFiles.includes(file);
|
||||
})
|
||||
.map(file => file.replace('.js', ''))
|
||||
.sort();
|
||||
|
||||
return migrations;
|
||||
}
|
||||
// Tự động phát hiện migrations
|
||||
const availableMigrations = discoverMigrations();
|
||||
/**
|
||||
* Hiển thị trạng thái của tất cả migrations
|
||||
*/
|
||||
async function showStatus() {
|
||||
const mongoose = require('mongoose');
|
||||
let ownConn = false;
|
||||
|
||||
try {
|
||||
const wasConnected = mongoose.connection.readyState === 1;
|
||||
await connectDB();
|
||||
if (!wasConnected) ownConn = true;
|
||||
|
||||
console.log('\nMigration Status:\n');
|
||||
|
||||
const ranMigrations = await migrationHelper.getRanMigrations();
|
||||
const ranMap = new Map();
|
||||
ranMigrations.forEach(m => ranMap.set(m.name, m));
|
||||
|
||||
// Tính toán độ rộng cột
|
||||
const maxNameLength = Math.max(...availableMigrations.map(name => name.length), 20);
|
||||
const statusWidth = 10;
|
||||
const batchWidth = 6;
|
||||
const ranAtWidth = 20;
|
||||
const totalWidth = maxNameLength + statusWidth + batchWidth + ranAtWidth + 11; // 11 = spaces and separators
|
||||
|
||||
// Header
|
||||
console.log('='.repeat(totalWidth));
|
||||
console.log(
|
||||
`${'Migration Name'.padEnd(maxNameLength)} | ${'Status'.padEnd(statusWidth)} | ${'Batch'.padEnd(batchWidth)} | Ran At`
|
||||
);
|
||||
console.log('='.repeat(totalWidth));
|
||||
|
||||
let pendingCount = 0;
|
||||
let ranCount = 0;
|
||||
|
||||
for (const migrationName of availableMigrations) {
|
||||
const migration = ranMap.get(migrationName);
|
||||
if (migration) {
|
||||
const ranAt = new Date(migration.ranAt).toLocaleString('vi-VN');
|
||||
console.log(
|
||||
`${migrationName.padEnd(maxNameLength)} | ${'Ran'.padEnd(statusWidth)} | ${String(migration.batch).padEnd(batchWidth)} | ${ranAt}`
|
||||
);
|
||||
ranCount++;
|
||||
} else {
|
||||
console.log(
|
||||
`${migrationName.padEnd(maxNameLength)} | ${'Pending'.padEnd(statusWidth)} | ${'-'.padEnd(batchWidth)} | -`
|
||||
);
|
||||
pendingCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('='.repeat(totalWidth));
|
||||
console.log(`\nSummary:`);
|
||||
console.log(` Ran: ${ranCount} migration(s)`);
|
||||
console.log(` Pending: ${pendingCount} migration(s)`);
|
||||
|
||||
const lastBatch = await migrationHelper.getLastBatch();
|
||||
if (lastBatch > 0) {
|
||||
console.log(` Last batch: ${lastBatch}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
if (ownConn && mongoose.connection.readyState === 1) {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
// Chạy nếu được gọi trực tiếp
|
||||
if (require.main === module) {
|
||||
showStatus();
|
||||
}
|
||||
module.exports = { showStatus };
|
||||
@@ -1036,12 +1036,12 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle <%= ['/admin/about-us','/admin/partnerships','/admin/history','/admin/accreditation','/admin/admissions','/admin/policies'].includes(currentPath) ? 'active' : '' %>"
|
||||
href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle <%= ['/admin/about','/admin/partnerships','/admin/history','/admin/accreditation','/admin/admissions','/admin/policies'].includes(currentPath) ? 'active' : '' %>"
|
||||
href="/admin/about" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
About
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item <%= currentPath === '/admin/about-us' ? 'active' : '' %>" href="/admin/about-us">About Us</a></li>
|
||||
<li><a class="dropdown-item <%= currentPath === '/admin/about' ? 'active' : '' %>" href="/admin/about">About Us</a></li>
|
||||
<li><a class="dropdown-item <%= currentPath === '/admin/partnerships' ? 'active' : '' %>" href="/admin/partnerships">Partnerships</a></li>
|
||||
<li><a class="dropdown-item <%= currentPath === '/admin/history' ? 'active' : '' %>" href="/admin/history">History</a></li>
|
||||
<li><a class="dropdown-item <%= currentPath === '/admin/accreditation' ? 'active' : '' %>" href="/admin/accreditation">Accreditation</a></li>
|
||||
|
||||
Reference in New Issue
Block a user