Files
cms.uldp.edu.vn/scripts/2026_04_10_210000_repair_about_contract.js
Tống Thành Đạt c6a2d4a55d 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
2026-04-10 22:32:51 +07:00

170 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };