forked from UKSOURCE/cms.hailearning.edu.vn
feat: enhance about us and footer CMS admin panels
- Improve aboutUs controller with better field handling - Update footer controller with expanded content management - Refine about admin view templates - Update appointment and footer admin views - Add about contract repair migration script - Update about.json seed data
This commit is contained in:
169
scripts/2026_04_10_210000_repair_about_contract.js
Normal file
169
scripts/2026_04_10_210000_repair_about_contract.js
Normal file
@@ -0,0 +1,169 @@
|
||||
const mongoose = require("mongoose");
|
||||
const connectDB = require("../config/database");
|
||||
const AboutUs = require("../models/aboutUs");
|
||||
const jsonHelper = require("../utils/jsonHelper");
|
||||
|
||||
const GENERIC_PLACEHOLDER = "7281.jpg";
|
||||
|
||||
const ABOUT_DEFAULTS = {
|
||||
heroBackground: "/uploads/about/breadcrumb.jpg",
|
||||
featuresBackground: "/assets/img/home-3/choose-us/pricing-bg.jpg",
|
||||
featuresImage: "/uploads/about/businessman.jpg",
|
||||
missionIcons: [
|
||||
"/assets/img/home-3/choose-us/icon-1.png",
|
||||
"/assets/img/home-3/choose-us/icon-2.png",
|
||||
"/assets/img/home-3/choose-us/icon-3.png",
|
||||
],
|
||||
featureIcons: [
|
||||
"/assets/img/home-3/choose-us/icon-1.png",
|
||||
"/assets/img/home-3/choose-us/icon-2.png",
|
||||
"/assets/img/home-3/choose-us/icon-3.png",
|
||||
],
|
||||
};
|
||||
|
||||
const trimString = (value) =>
|
||||
typeof value === "string" ? value.trim() : "";
|
||||
|
||||
const normalizePath = (value) => {
|
||||
const trimmed = trimString(value);
|
||||
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||
};
|
||||
|
||||
const isPlaceholderLike = (value) => {
|
||||
const normalized = normalizePath(value).toLowerCase();
|
||||
return !normalized || normalized.endsWith(`/${GENERIC_PLACEHOLDER}`);
|
||||
};
|
||||
|
||||
const normalizeButton = (value = {}) => ({
|
||||
label: trimString(value?.label),
|
||||
href: trimString(value?.href),
|
||||
});
|
||||
|
||||
const repairIconItems = (items, fallbacks) =>
|
||||
Array.isArray(items)
|
||||
? items.map((item, index) => ({
|
||||
...item,
|
||||
icon: isPlaceholderLike(item?.icon)
|
||||
? fallbacks[index % fallbacks.length]
|
||||
: normalizePath(item?.icon),
|
||||
}))
|
||||
: [];
|
||||
|
||||
const buildCanonicalAboutData = (source = {}) => ({
|
||||
hero: {
|
||||
title: trimString(source?.hero?.title),
|
||||
breadcrumb: Array.isArray(source?.hero?.breadcrumb)
|
||||
? source.hero.breadcrumb.map(trimString).filter(Boolean)
|
||||
: [],
|
||||
backgroundImage: isPlaceholderLike(source?.hero?.backgroundImage)
|
||||
? ABOUT_DEFAULTS.heroBackground
|
||||
: normalizePath(source?.hero?.backgroundImage),
|
||||
},
|
||||
intro: {
|
||||
subheading: trimString(source?.intro?.subheading),
|
||||
heading: trimString(source?.intro?.heading),
|
||||
description: trimString(source?.intro?.description),
|
||||
image: normalizePath(source?.intro?.image),
|
||||
},
|
||||
mission: {
|
||||
subheading: trimString(source?.mission?.subheading),
|
||||
heading: trimString(source?.mission?.heading),
|
||||
description: trimString(source?.mission?.description),
|
||||
images: {
|
||||
main: normalizePath(source?.mission?.images?.main),
|
||||
secondary: normalizePath(source?.mission?.images?.secondary),
|
||||
},
|
||||
items: repairIconItems(source?.mission?.items, ABOUT_DEFAULTS.missionIcons)
|
||||
.map((item) => ({
|
||||
icon: item.icon,
|
||||
label: trimString(item?.label),
|
||||
description: trimString(item?.description),
|
||||
}))
|
||||
.filter((item) => item.icon || item.label || item.description),
|
||||
features: Array.isArray(source?.mission?.features)
|
||||
? source.mission.features.map(trimString).filter(Boolean)
|
||||
: [],
|
||||
ctaButton: normalizeButton(source?.mission?.ctaButton),
|
||||
},
|
||||
features: {
|
||||
backgroundImage: isPlaceholderLike(source?.features?.backgroundImage)
|
||||
? ABOUT_DEFAULTS.featuresBackground
|
||||
: normalizePath(source?.features?.backgroundImage),
|
||||
subheading: trimString(source?.features?.subheading),
|
||||
heading: trimString(source?.features?.heading),
|
||||
description: trimString(source?.features?.description),
|
||||
image: isPlaceholderLike(source?.features?.image)
|
||||
? ABOUT_DEFAULTS.featuresImage
|
||||
: normalizePath(source?.features?.image),
|
||||
items: repairIconItems(source?.features?.items, ABOUT_DEFAULTS.featureIcons)
|
||||
.map((item) => ({
|
||||
icon: item.icon,
|
||||
title: trimString(item?.title),
|
||||
description: trimString(item?.description),
|
||||
}))
|
||||
.filter((item) => item.icon || item.title || item.description),
|
||||
ctaButton: normalizeButton(source?.features?.ctaButton),
|
||||
},
|
||||
news: {
|
||||
subheading: trimString(source?.news?.subheading),
|
||||
heading: trimString(source?.news?.heading),
|
||||
ctaButton: normalizeButton(source?.news?.ctaButton),
|
||||
selectedBlogIds: Array.isArray(source?.news?.selectedBlogIds)
|
||||
? source.news.selectedBlogIds.filter(Boolean)
|
||||
: [],
|
||||
items: [],
|
||||
},
|
||||
});
|
||||
|
||||
async function up() {
|
||||
await connectDB();
|
||||
|
||||
try {
|
||||
const doc = await AboutUs.getSingle();
|
||||
const repaired = buildCanonicalAboutData(doc.toObject());
|
||||
|
||||
doc.set(repaired);
|
||||
await doc.save();
|
||||
|
||||
jsonHelper.writeJsonFile("about", repaired);
|
||||
|
||||
console.log("✓ Repaired About CMS contract");
|
||||
console.log(` - Database: ${mongoose.connection.db.databaseName}`);
|
||||
console.log(" - Canonicalized About singleton fields");
|
||||
console.log(" - Backfilled hero/features/images/icons when placeholder-like");
|
||||
} catch (error) {
|
||||
console.error("✗ Failed to repair About contract:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await mongoose.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
async function down() {
|
||||
console.log(
|
||||
"ℹ Rollback skipped for 2026_04_10_210000_repair_about_contract because the migration normalizes live content in place.",
|
||||
);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
up()
|
||||
.then(() => {
|
||||
console.log("\n✓ Migration script completed");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("\n✗ Migration script failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { up, down };
|
||||
Reference in New Issue
Block a user