// 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();