forked from UKSOURCE/cms.hailearning.edu.vn
UI Visa-VisaDetail
This commit is contained in:
336
scripts/2026_02_03_645124_visa.js
Normal file
336
scripts/2026_02_03_645124_visa.js
Normal file
@@ -0,0 +1,336 @@
|
||||
// scripts/migrateVisa.js
|
||||
|
||||
require("dotenv").config();
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const mongoose = require("mongoose");
|
||||
const Visa = require("../models/visa");
|
||||
|
||||
// 1. Đọc file JSON
|
||||
async function loadVisaData() {
|
||||
const filePath = path.join(__dirname, "..", "data", "visa.json");
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
// 2. Hàm Transform: Đổ dữ liệu từ JSON vào đúng Schema
|
||||
function transformVisa(sourceData) {
|
||||
// JSON có structure hero.title và hero.summaryList
|
||||
return {
|
||||
hero: {
|
||||
title: sourceData.hero?.title || "Visa",
|
||||
summaryList: Array.isArray(sourceData.hero?.summaryList)
|
||||
? sourceData.hero.summaryList.map((country) =>
|
||||
transformCountry(country),
|
||||
)
|
||||
: [],
|
||||
},
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform individual country
|
||||
function transformCountry(source) {
|
||||
return {
|
||||
id: source.id || 0,
|
||||
name: source.name || "",
|
||||
slug: source.slug || "",
|
||||
icon: source.icon || "",
|
||||
services: Array.isArray(source.services) ? source.services : [],
|
||||
detailedView: source.detailedView
|
||||
? transformDetailedView(source.detailedView)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform DetailedView
|
||||
function transformDetailedView(source) {
|
||||
return {
|
||||
activeCountry: source.activeCountry
|
||||
? transformActiveCountry(source.activeCountry)
|
||||
: null,
|
||||
relatedCountries: Array.isArray(source.relatedCountries)
|
||||
? source.relatedCountries.map((country) => ({
|
||||
id: country.id || 0,
|
||||
name: country.name || "",
|
||||
icon: country.icon || "",
|
||||
}))
|
||||
: [],
|
||||
contactInfo: source.contactInfo
|
||||
? transformContactInfo(source.contactInfo)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform ActiveCountry
|
||||
function transformActiveCountry(source) {
|
||||
return {
|
||||
id: source.id || 0,
|
||||
name: source.name || "",
|
||||
title: source.title || "",
|
||||
mainImage: source.mainImage || "",
|
||||
description: source.description || "",
|
||||
additionalInfo: source.additionalInfo || "",
|
||||
tagline: source.tagline || "",
|
||||
visaTypes: Array.isArray(source.visaTypes)
|
||||
? source.visaTypes.map((type) => ({
|
||||
category: type.category || "",
|
||||
items: Array.isArray(type.items)
|
||||
? type.items.map((item) => ({
|
||||
title: item.title || "",
|
||||
description: item.description || "",
|
||||
}))
|
||||
: [],
|
||||
}))
|
||||
: [],
|
||||
visaProcess: source.visaProcess
|
||||
? transformVisaProcess(source.visaProcess)
|
||||
: null,
|
||||
gallery: Array.isArray(source.gallery) ? source.gallery : [],
|
||||
visaCategories: source.visaCategories
|
||||
? transformVisaCategories(source.visaCategories)
|
||||
: null,
|
||||
visaService: source.visaService
|
||||
? transformVisaService(source.visaService)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform VisaProcess
|
||||
function transformVisaProcess(source) {
|
||||
return {
|
||||
title: source.title || "",
|
||||
steps: Array.isArray(source.steps)
|
||||
? source.steps.map((step) => ({
|
||||
number: step.number || "",
|
||||
title: step.title || "",
|
||||
description: step.description || "",
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform VisaCategories
|
||||
function transformVisaCategories(source) {
|
||||
return {
|
||||
title: source.title || "",
|
||||
steps: Array.isArray(source.steps) ? source.steps : [],
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform VisaService
|
||||
function transformVisaService(source) {
|
||||
return {
|
||||
title: source.title || "",
|
||||
steps: Array.isArray(source.steps)
|
||||
? source.steps.map((step) => ({
|
||||
number: step.number || "",
|
||||
title: step.title || "",
|
||||
description: step.description || "",
|
||||
}))
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function: Transform ContactInfo
|
||||
function transformContactInfo(source) {
|
||||
return {
|
||||
img: source.img || "",
|
||||
sectionTitle: source.sectionTitle || "Visa & Immigration",
|
||||
helpText: source.helpText || "Need Help?",
|
||||
phone: {
|
||||
label: source.phone?.label || "Call Us",
|
||||
value: source.phone?.value || "",
|
||||
link: source.phone?.link || "",
|
||||
},
|
||||
email: {
|
||||
label: source.email?.label || "Mail Us",
|
||||
value: source.email?.value || "",
|
||||
link: source.email?.link || "",
|
||||
},
|
||||
location: {
|
||||
label: source.location?.label || "Location",
|
||||
address: source.location?.address || "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Validate data before migration
|
||||
function validateVisaData(visaData) {
|
||||
const errors = [];
|
||||
|
||||
if (!visaData.hero) {
|
||||
errors.push("Missing hero section");
|
||||
}
|
||||
|
||||
if (!visaData.hero?.title) {
|
||||
console.warn("⚠️ Hero title is missing, using default 'Visa'");
|
||||
}
|
||||
|
||||
if (!Array.isArray(visaData.hero?.summaryList)) {
|
||||
errors.push("summaryList must be an array");
|
||||
} else if (visaData.hero.summaryList.length === 0) {
|
||||
errors.push("summaryList is empty");
|
||||
} else {
|
||||
// Validate each country
|
||||
visaData.hero.summaryList.forEach((country, idx) => {
|
||||
if (!country.name || !country.slug) {
|
||||
errors.push(`Country at index ${idx}: missing name or slug`);
|
||||
}
|
||||
|
||||
if (country.detailedView) {
|
||||
if (!country.detailedView.activeCountry) {
|
||||
console.warn(
|
||||
`⚠️ Country "${country.name}" (${idx}): missing activeCountry details`,
|
||||
);
|
||||
}
|
||||
if (!Array.isArray(country.detailedView.relatedCountries)) {
|
||||
errors.push(
|
||||
`Country "${country.name}" (${idx}): relatedCountries must be array`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Helper function: Get data summary
|
||||
function getDataSummary(visaData) {
|
||||
const summary = {
|
||||
heroTitle: visaData.hero?.title || "N/A",
|
||||
totalCountries: visaData.hero?.summaryList?.length || 0,
|
||||
withDetails: 0,
|
||||
withoutDetails: 0,
|
||||
byCountry: [],
|
||||
};
|
||||
|
||||
if (visaData.hero?.summaryList) {
|
||||
visaData.hero.summaryList.forEach((country) => {
|
||||
const hasDetails = !!country.detailedView?.activeCountry;
|
||||
const relatedCount = country.detailedView?.relatedCountries?.length || 0;
|
||||
|
||||
if (hasDetails) {
|
||||
summary.withDetails++;
|
||||
} else {
|
||||
summary.withoutDetails++;
|
||||
}
|
||||
|
||||
summary.byCountry.push({
|
||||
name: country.name,
|
||||
slug: country.slug,
|
||||
hasDetails,
|
||||
relatedCountries: relatedCount,
|
||||
services: country.services?.length || 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// 4. Chạy Migration
|
||||
async function migrate() {
|
||||
try {
|
||||
// Kết nối DB
|
||||
console.log("🔗 Connecting to MongoDB...");
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
console.log("✅ Connected to MongoDB\n");
|
||||
|
||||
// A. Lấy dữ liệu thô
|
||||
console.log("📖 Loading visa data from JSON...");
|
||||
const rawData = await loadVisaData();
|
||||
console.log("✅ JSON data loaded\n");
|
||||
|
||||
// B. Chuẩn hóa dữ liệu theo Schema
|
||||
console.log("🔄 Transforming data structure...");
|
||||
const visaData = transformVisa(rawData);
|
||||
console.log("✅ Data transformation completed\n");
|
||||
|
||||
// C. Validate dữ liệu
|
||||
console.log("✔️ Validating data structure...");
|
||||
const errors = validateVisaData(visaData);
|
||||
if (errors.length > 0) {
|
||||
console.error("❌ Validation errors found:");
|
||||
errors.forEach((err, idx) => console.error(` ${idx + 1}. ${err}`));
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("✅ Data validation passed\n");
|
||||
|
||||
// D. Get summary
|
||||
const summary = getDataSummary(visaData);
|
||||
|
||||
console.log("📊 Migration Summary:");
|
||||
console.log(` Hero Title: "${summary.heroTitle}"`);
|
||||
console.log(` Total countries: ${summary.totalCountries}`);
|
||||
console.log(` With details: ${summary.withDetails}`);
|
||||
console.log(` Without details: ${summary.withoutDetails}`);
|
||||
console.log(`\n Country Details:`);
|
||||
|
||||
summary.byCountry.forEach((country) => {
|
||||
const detailBadge = country.hasDetails ? "✅" : "❌";
|
||||
const detailText = country.hasDetails
|
||||
? `(${country.relatedCountries} related)`
|
||||
: "(basic only)";
|
||||
console.log(
|
||||
` ${detailBadge} ${country.name.padEnd(20)} (${country.slug.padEnd(
|
||||
12,
|
||||
)}) - ${country.services} services ${detailText}`,
|
||||
);
|
||||
});
|
||||
console.log("");
|
||||
|
||||
// E. Lưu vào DB (Upsert: Có rồi thì update, chưa có thì tạo)
|
||||
const existingDoc = await Visa.findOne().sort({ updatedAt: -1 });
|
||||
|
||||
if (existingDoc) {
|
||||
console.log("📝 Updating existing Visa document...");
|
||||
console.log(` Document ID: ${existingDoc._id}`);
|
||||
|
||||
const updated = await Visa.findByIdAndUpdate(
|
||||
existingDoc._id,
|
||||
{ $set: visaData },
|
||||
{ new: true },
|
||||
);
|
||||
|
||||
console.log("✅ Visa document updated successfully");
|
||||
console.log(` Updated at: ${updated.updatedAt}`);
|
||||
} else {
|
||||
console.log("📝 Creating NEW Visa document...");
|
||||
|
||||
const newDoc = await Visa.create(visaData);
|
||||
|
||||
console.log("✅ Visa document created successfully");
|
||||
console.log(` Document ID: ${newDoc._id}`);
|
||||
console.log(` Created at: ${newDoc.createdAt}`);
|
||||
}
|
||||
|
||||
console.log("\n✨ Visa migration completed successfully!");
|
||||
} catch (error) {
|
||||
console.error("\n❌ Migration failed:");
|
||||
console.error(` Error: ${error.message}`);
|
||||
|
||||
if (error.name === "ValidationError") {
|
||||
console.error("\n Validation Errors:");
|
||||
Object.keys(error.errors).forEach((field) => {
|
||||
console.error(` - ${field}: ${error.errors[field].message}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (error.stack) {
|
||||
console.error("\n📋 Stack trace:");
|
||||
console.error(error.stack);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await mongoose.connection.close();
|
||||
console.log("\n🔌 MongoDB connection closed");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration
|
||||
console.log("🚀 Starting Visa Migration...\n");
|
||||
migrate();
|
||||
Reference in New Issue
Block a user