forked from UKSOURCE/cms.lams
Refactor Homepage, About, Footer (Controller,Model, View, Data), Update: Dashboard, Delete old file
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
require("dotenv").config();
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const connectDB = require("../config/database");
|
||||
const Contact = require("../models/contact");
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
/**
|
||||
* Migration: contact
|
||||
* Migrate contact data from contact-data.json
|
||||
*/
|
||||
async function migrate() {
|
||||
try {
|
||||
await connectDB();
|
||||
|
||||
// Read contact-data.json file
|
||||
const contactJsonPath = path.join(__dirname, "../data/contact.json");
|
||||
const contactData = JSON.parse(await fs.readFile(contactJsonPath, "utf8"));
|
||||
|
||||
// Migrate data using the model's static method
|
||||
await Contact.migrateFromJson(contactData);
|
||||
|
||||
console.log("Contact migration completed successfully");
|
||||
|
||||
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 };
|
||||
@@ -1,186 +0,0 @@
|
||||
require("dotenv").config();
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const connectDB = require("../config/database");
|
||||
const Service = require("../models/service");
|
||||
|
||||
/**
|
||||
* Transform service.json data to match Service schema
|
||||
*/
|
||||
function transformServiceData(sourceData) {
|
||||
return {
|
||||
pageTitle: sourceData.pageTitle || "",
|
||||
|
||||
// Breadcrumb navigation section
|
||||
breadcrumb: {
|
||||
title: sourceData?.breadcrumb?.title || "",
|
||||
backgroundImage: sourceData?.breadcrumb?.backgroundImage || "",
|
||||
shape: sourceData?.breadcrumb?.shape || "",
|
||||
items: Array.isArray(sourceData?.breadcrumb?.items)
|
||||
? sourceData.breadcrumb.items.map((item) => ({
|
||||
label: item.label || "",
|
||||
href: item.href || "",
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
|
||||
// Main services section
|
||||
services: {
|
||||
title: {
|
||||
subTitle: sourceData?.services?.title?.subTitle || "",
|
||||
mainTitle: sourceData?.services?.title?.mainTitle || "",
|
||||
},
|
||||
items: Array.isArray(sourceData?.services?.items)
|
||||
? sourceData.services.items.map((service) => ({
|
||||
slug: service.slug || "",
|
||||
name: service.name || "",
|
||||
description: service.description || "",
|
||||
image: service.image || "",
|
||||
layout: service.layout || "",
|
||||
details: {
|
||||
title: service.details?.title || "",
|
||||
description: service.details?.description || "",
|
||||
mainImage: service.details?.mainImage || "",
|
||||
overviewTitle: service.details?.overviewTitle || "",
|
||||
overviewDescription: service.details?.overviewDescription || "",
|
||||
additionalDescription:
|
||||
service.details?.additionalDescription || "",
|
||||
keyFeaturesTitle: service.details?.keyFeaturesTitle || "",
|
||||
keyFeaturesImage: service.details?.keyFeaturesImage || "",
|
||||
features: Array.isArray(service.details?.features)
|
||||
? service.details.features.map((feature) => ({
|
||||
icon: feature.icon || "",
|
||||
title: feature.title || "",
|
||||
description: feature.description || "",
|
||||
}))
|
||||
: [],
|
||||
faqTitle: service.details?.faqTitle || "",
|
||||
faqImage: service.details?.faqImage || "",
|
||||
faq: Array.isArray(service.details?.faq)
|
||||
? service.details.faq.map((faqItem) => ({
|
||||
id: faqItem.id || "",
|
||||
question: faqItem.question || "",
|
||||
answer: faqItem.answer || "",
|
||||
isExpanded: faqItem.isExpanded || false,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
|
||||
// Destination countries section
|
||||
destinations: {
|
||||
backgroundImage: sourceData?.destinations?.backgroundImage || "",
|
||||
title: {
|
||||
subTitle: sourceData?.destinations?.title?.subTitle || "",
|
||||
mainTitle: sourceData?.destinations?.title?.mainTitle || "",
|
||||
},
|
||||
items: Array.isArray(sourceData?.destinations?.items)
|
||||
? sourceData.destinations.items.map((country) => ({
|
||||
id: country.id || "",
|
||||
name: country.name || "",
|
||||
description: country.description || "",
|
||||
image: country.image || "",
|
||||
icon: country.icon || "",
|
||||
link: country.link || "",
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
|
||||
// Visa types section
|
||||
visas: {
|
||||
items: Array.isArray(sourceData?.visas?.items)
|
||||
? sourceData.visas.items.map((visa) => ({
|
||||
id: visa.id || "",
|
||||
number: visa.number || "",
|
||||
name: visa.name || "",
|
||||
description: visa.description || "",
|
||||
buttonText: visa.buttonText || "",
|
||||
buttonLink: visa.buttonLink || "",
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
|
||||
// Client reviews section
|
||||
reviews: {
|
||||
title: {
|
||||
subTitle: sourceData?.reviews?.title?.subTitle || "",
|
||||
mainTitle: sourceData?.reviews?.title?.mainTitle || "",
|
||||
},
|
||||
viewAllButton: {
|
||||
text: sourceData?.reviews?.viewAllButton?.text || "",
|
||||
icon: sourceData?.reviews?.viewAllButton?.icon || "",
|
||||
link: sourceData?.reviews?.viewAllButton?.link || "",
|
||||
},
|
||||
thumb: sourceData?.reviews?.thumb || "",
|
||||
items: Array.isArray(sourceData?.reviews?.items)
|
||||
? sourceData.reviews.items.map((review) => ({
|
||||
id: review.id || "",
|
||||
rating: review.rating || 5,
|
||||
content: review.content || "",
|
||||
author: {
|
||||
name: review.author?.name || "",
|
||||
type: review.author?.type || "",
|
||||
},
|
||||
icon: review.icon || "",
|
||||
}))
|
||||
: [],
|
||||
navigation: {
|
||||
prevButton: sourceData?.reviews?.navigation?.prevButton || "",
|
||||
nextButton: sourceData?.reviews?.navigation?.nextButton || "",
|
||||
prevIcon: sourceData?.reviews?.navigation?.prevIcon || "",
|
||||
nextIcon: sourceData?.reviews?.navigation?.nextIcon || "",
|
||||
},
|
||||
},
|
||||
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration function for service page data
|
||||
*/
|
||||
async function migrateServiceData() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log("🚀 Starting service page migration...");
|
||||
|
||||
// Clear existing service documents
|
||||
await Service.deleteMany({});
|
||||
console.log("🗑️ Cleared existing service documents");
|
||||
|
||||
// Read service.json file
|
||||
const serviceJsonPath = path.join(__dirname, "..", "data", "service.json");
|
||||
const rawJsonData = await fs.readFile(serviceJsonPath, "utf8");
|
||||
const sourceServiceData = JSON.parse(rawJsonData);
|
||||
|
||||
// Transform data to match schema
|
||||
const transformedServiceData = transformServiceData(sourceServiceData);
|
||||
|
||||
// Create new service document
|
||||
const newService = new Service(transformedServiceData);
|
||||
const savedService = await newService.save();
|
||||
|
||||
console.log("✅ Service page migration completed successfully!");
|
||||
console.log(`📄 Service document ID: ${savedService._id}`);
|
||||
|
||||
await mongoose.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("❌ Service migration error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration if called directly
|
||||
if (require.main === module) {
|
||||
migrateServiceData();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
migrate: migrateServiceData,
|
||||
transformServiceData,
|
||||
};
|
||||
@@ -1,182 +0,0 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const connectDB = require('../config/database');
|
||||
|
||||
/**
|
||||
* Migration: create_complete_blog_system
|
||||
* Created: 17:00:00 2/2/2026
|
||||
* Description: Tạo hoàn chỉnh hệ thống blog với categories, tags, posts và comments
|
||||
*/
|
||||
async function migrate() {
|
||||
try {
|
||||
// Kết nối database
|
||||
await connectDB();
|
||||
console.log('🚀 Starting migration: create_complete_blog_system...');
|
||||
|
||||
// Import models
|
||||
const Blog = require('../models/blog');
|
||||
const BlogCategory = require('../models/blogCategory');
|
||||
const BlogTag = require('../models/blogTag');
|
||||
const BlogComment = require('../models/blogComment');
|
||||
const RecentPost = require('../models/recentPost');
|
||||
|
||||
console.log('✅ Blog models registered successfully');
|
||||
|
||||
// Load complete data
|
||||
const dataPath = path.join(__dirname, '..', 'data', 'blog.json');
|
||||
const rawData = await fs.readFile(dataPath, 'utf8');
|
||||
const data = JSON.parse(rawData);
|
||||
|
||||
console.log('📖 Complete blog data loaded from JSON');
|
||||
|
||||
// Clear existing data
|
||||
console.log('🧹 Clearing existing blog data...');
|
||||
await BlogComment.deleteMany({});
|
||||
await Blog.deleteMany({});
|
||||
await BlogCategory.deleteMany({});
|
||||
await BlogTag.deleteMany({});
|
||||
await RecentPost.deleteMany({});
|
||||
console.log('✅ Existing data cleared');
|
||||
|
||||
// 1. Create categories
|
||||
console.log('📝 Creating categories...');
|
||||
const createdCategories = [];
|
||||
for (const categoryData of data.categories) {
|
||||
const category = new BlogCategory(categoryData);
|
||||
await category.save();
|
||||
createdCategories.push(category);
|
||||
console.log(`✅ Created category: ${category.name}`);
|
||||
}
|
||||
|
||||
// 2. Create tags
|
||||
console.log('📝 Creating tags...');
|
||||
const createdTags = [];
|
||||
for (const tagData of data.tags) {
|
||||
const tag = new BlogTag(tagData);
|
||||
await tag.save();
|
||||
createdTags.push(tag);
|
||||
console.log(`✅ Created tag: ${tag.name}`);
|
||||
}
|
||||
|
||||
// 3. Create blog posts
|
||||
console.log('📝 Creating blog posts...');
|
||||
const createdPosts = [];
|
||||
for (const postData of data.posts) {
|
||||
const post = new Blog(postData);
|
||||
await post.save();
|
||||
createdPosts.push(post);
|
||||
console.log(`✅ Created blog post: ${post.title}`);
|
||||
}
|
||||
|
||||
// 4. Create comments
|
||||
console.log('💬 Creating comments...');
|
||||
let createdCommentsCount = 0;
|
||||
|
||||
for (const commentData of data.comments) {
|
||||
// Find the blog post by slug
|
||||
const blog = await Blog.findOne({
|
||||
slug: commentData.postSlug,
|
||||
status: 'published'
|
||||
});
|
||||
|
||||
if (blog) {
|
||||
const comment = new BlogComment({
|
||||
postId: blog._id,
|
||||
authorName: commentData.authorName,
|
||||
authorAvatar: commentData.authorAvatar,
|
||||
content: commentData.content,
|
||||
createdAt: commentData.createdAt,
|
||||
status: commentData.status
|
||||
});
|
||||
|
||||
await comment.save();
|
||||
createdCommentsCount++;
|
||||
console.log(`✅ Created comment by ${comment.authorName} for: ${blog.title}`);
|
||||
} else {
|
||||
console.log(`⚠️ Blog post not found for slug: ${commentData.postSlug}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Update category post counts
|
||||
console.log('📊 Updating category post counts...');
|
||||
for (const category of createdCategories) {
|
||||
await category.updatePostCount();
|
||||
console.log(`📊 Category "${category.name}": ${category.postCount} posts`);
|
||||
}
|
||||
|
||||
// 6. Update tag post counts
|
||||
console.log('📊 Updating tag post counts...');
|
||||
for (const tag of createdTags) {
|
||||
await tag.updatePostCount();
|
||||
console.log(`📊 Tag "${tag.name}": ${tag.postCount} posts`);
|
||||
}
|
||||
|
||||
// 7. Update comments count in blog posts
|
||||
console.log('📊 Updating comments count in blog posts...');
|
||||
const blogs = await Blog.find({ status: 'published' });
|
||||
|
||||
for (const blog of blogs) {
|
||||
const commentsCount = await BlogComment.countDocuments({
|
||||
postId: blog._id,
|
||||
status: 'approved'
|
||||
});
|
||||
|
||||
blog.commentsCount = commentsCount;
|
||||
await blog.save();
|
||||
|
||||
if (commentsCount > 0) {
|
||||
console.log(`📊 Updated comments count for "${blog.title}": ${commentsCount} comments`);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Sync recent posts
|
||||
console.log('🔄 Syncing recent posts...');
|
||||
await RecentPost.syncFromBlogs(5);
|
||||
const recentPostsCount = await RecentPost.countDocuments();
|
||||
console.log(`🔄 Synced ${recentPostsCount} recent posts`);
|
||||
|
||||
// Final summary
|
||||
console.log('\n🎉 Migration create_complete_blog_system completed successfully!');
|
||||
console.log('=' .repeat(60));
|
||||
console.log('📊 MIGRATION SUMMARY:');
|
||||
console.log(` ✅ Categories: ${createdCategories.length}`);
|
||||
console.log(` ✅ Tags: ${createdTags.length}`);
|
||||
console.log(` ✅ Blog Posts: ${createdPosts.length}`);
|
||||
console.log(` ✅ Comments: ${createdCommentsCount}`);
|
||||
console.log(` ✅ Recent Posts: ${recentPostsCount}`);
|
||||
|
||||
// Statistics
|
||||
const totalPublishedPosts = await Blog.countDocuments({ status: 'published' });
|
||||
const totalFeaturedPosts = await Blog.countDocuments({ status: 'published', isFeatured: true });
|
||||
const totalApprovedComments = await BlogComment.countDocuments({ status: 'approved' });
|
||||
|
||||
console.log('\n📈 SYSTEM STATISTICS:');
|
||||
console.log(` 📝 Published Posts: ${totalPublishedPosts}`);
|
||||
console.log(` ⭐ Featured Posts: ${totalFeaturedPosts}`);
|
||||
console.log(` 💬 Approved Comments: ${totalApprovedComments}`);
|
||||
|
||||
console.log('\n🌐 ACCESS POINTS:');
|
||||
console.log(' 📱 Admin Panel: http://localhost:3001/admin/blog');
|
||||
console.log(' 🔗 API Endpoint: http://localhost:3001/api/blog');
|
||||
console.log(' 📊 Categories API: http://localhost:3001/api/blog-categories');
|
||||
console.log(' 🏷️ Tags API: http://localhost:3001/api/blog-tags');
|
||||
|
||||
console.log('\n✨ Blog system is now ready for use!');
|
||||
console.log('=' .repeat(60));
|
||||
|
||||
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 };
|
||||
@@ -1,336 +0,0 @@
|
||||
// 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();
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* Migration script for Appointment data
|
||||
* Imports data from appointment.json to MongoDB
|
||||
*
|
||||
* Run: node scripts/2026_02_03_appointment.js
|
||||
*/
|
||||
|
||||
require("dotenv").config();
|
||||
const mongoose = require("mongoose");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Connect to MongoDB
|
||||
const connectDB = async () => {
|
||||
try {
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
console.log("MongoDB connected successfully");
|
||||
} catch (error) {
|
||||
console.error("MongoDB connection error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const runMigration = async () => {
|
||||
try {
|
||||
await connectDB();
|
||||
|
||||
// Load Appointment model
|
||||
const Appointment = require("../models/appointment");
|
||||
|
||||
// Load JSON data
|
||||
const jsonPath = path.join(__dirname, "../data/appointment.json");
|
||||
|
||||
if (!fs.existsSync(jsonPath)) {
|
||||
console.log("appointment.json not found, creating default data...");
|
||||
const defaultData = {
|
||||
hero: {
|
||||
title: "Make Appointment",
|
||||
backgroundImage: "",
|
||||
subtitle: "",
|
||||
heading: "",
|
||||
description: "",
|
||||
},
|
||||
visaOptions: [],
|
||||
form: {
|
||||
heading: "Request Appointment",
|
||||
fields: [],
|
||||
submitButton: {
|
||||
text: "Request Appointment",
|
||||
icon: "fa-solid fa-arrow-right",
|
||||
buttonClass: "theme-btn",
|
||||
},
|
||||
},
|
||||
};
|
||||
await Appointment.migrateFromJson(defaultData);
|
||||
} else {
|
||||
const jsonData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
||||
console.log("Loaded appointment.json data");
|
||||
await Appointment.migrateFromJson(jsonData);
|
||||
}
|
||||
|
||||
console.log("✅ Appointment migration completed successfully!");
|
||||
} catch (error) {
|
||||
console.error("❌ Migration failed:", error);
|
||||
} finally {
|
||||
await mongoose.connection.close();
|
||||
console.log("MongoDB connection closed");
|
||||
}
|
||||
};
|
||||
|
||||
runMigration();
|
||||
@@ -1,68 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
// Import model
|
||||
const Footer = require("../models/footer");
|
||||
|
||||
/**
|
||||
* Migration script để import dữ liệu footer từ JSON
|
||||
*/
|
||||
async function up() {
|
||||
try {
|
||||
console.log("Starting footer migration...");
|
||||
|
||||
// Đọc dữ liệu từ file JSON
|
||||
const jsonPath = path.join(__dirname, "../data/footer.json");
|
||||
|
||||
if (!fs.existsSync(jsonPath)) {
|
||||
throw new Error("Footer JSON file not found");
|
||||
}
|
||||
|
||||
const footerData = JSON.parse(fs.readFileSync(jsonPath, "utf8"));
|
||||
|
||||
// Sử dụng static method từ model để migrate
|
||||
const result = await Footer.migrateFromJson(footerData);
|
||||
|
||||
console.log("Footer migration completed successfully");
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Footer migration failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback migration
|
||||
*/
|
||||
async function down() {
|
||||
try {
|
||||
console.log("Rolling back footer migration...");
|
||||
|
||||
// Xóa footer data
|
||||
await Footer.deleteMany({});
|
||||
|
||||
console.log("Footer rollback completed");
|
||||
} catch (error) {
|
||||
console.error("Footer rollback failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { up, down };
|
||||
|
||||
// Chạy migration nếu file được gọi trực tiếp
|
||||
if (require.main === module) {
|
||||
const connectDB = require("../config/database");
|
||||
|
||||
connectDB()
|
||||
.then(() => up())
|
||||
.then(() => {
|
||||
console.log("Migration completed successfully");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Migration failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const Footer = require("../models/footer");
|
||||
const footerData = require("../data/footer.json");
|
||||
|
||||
async function addFooterMenuOrder() {
|
||||
try {
|
||||
console.log("=== Adding order field to Footer Menu Links ===");
|
||||
|
||||
// Connect to database
|
||||
await mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost:27017/hailearning");
|
||||
console.log("✓ Connected to MongoDB");
|
||||
|
||||
// Get existing footer or create from JSON
|
||||
let footer = await Footer.findOne();
|
||||
|
||||
if (!footer) {
|
||||
console.log("No existing footer found, creating from JSON data...");
|
||||
|
||||
// Add order to bottom menu links
|
||||
if (footerData.bottom && footerData.bottom.menuLinks) {
|
||||
footerData.bottom.menuLinks = footerData.bottom.menuLinks.map((link, index) => ({
|
||||
...link,
|
||||
order: index + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
// Add order to top menu links
|
||||
if (footerData.top && footerData.top.menuLinks) {
|
||||
footerData.top.menuLinks = footerData.top.menuLinks.map((link, index) => ({
|
||||
...link,
|
||||
order: index + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
footer = await Footer.create(footerData);
|
||||
console.log("✓ Footer created with order fields");
|
||||
} else {
|
||||
console.log("Found existing footer, adding order fields...");
|
||||
|
||||
// Add order to bottom menu links
|
||||
if (footer.bottom && footer.bottom.menuLinks) {
|
||||
footer.bottom.menuLinks = footer.bottom.menuLinks.map((link, index) => ({
|
||||
label: link.label,
|
||||
href: link.href,
|
||||
order: link.order || index + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
// Add order to top menu links
|
||||
if (footer.top && footer.top.menuLinks) {
|
||||
footer.top.menuLinks = footer.top.menuLinks.map((link, index) => ({
|
||||
label: link.label,
|
||||
href: link.href,
|
||||
order: link.order || index + 1,
|
||||
}));
|
||||
}
|
||||
|
||||
await footer.save();
|
||||
console.log("✓ Footer updated with order fields");
|
||||
}
|
||||
|
||||
console.log("Bottom Menu Links with order:");
|
||||
footer.bottom.menuLinks.forEach((link, index) => {
|
||||
console.log(` ${index + 1}. ${link.label} (order: ${link.order}) -> ${link.href}`);
|
||||
});
|
||||
|
||||
console.log("=== Footer Menu Order Migration Completed ===");
|
||||
} catch (error) {
|
||||
console.error("✗ Migration failed:", error);
|
||||
throw error;
|
||||
} finally {
|
||||
await mongoose.disconnect();
|
||||
console.log("✓ Disconnected from MongoDB");
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration if called directly
|
||||
if (require.main === module) {
|
||||
addFooterMenuOrder()
|
||||
.then(() => {
|
||||
console.log("Migration completed successfully");
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Migration failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = addFooterMenuOrder;
|
||||
@@ -1,47 +0,0 @@
|
||||
require("dotenv").config();
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const connectDB = require("../config/database");
|
||||
|
||||
/**
|
||||
* Migration: import_home_content
|
||||
* Created: 19:00:00 2026-02-05
|
||||
* Description:
|
||||
* Import nội dung trang Home từ file JSON (Next.js) vào MongoDB (model Home).
|
||||
* Nguồn dữ liệu: hailearning.edu.vn/app/home.json
|
||||
*/
|
||||
async function migrate() {
|
||||
try {
|
||||
// 1) Connect DB
|
||||
await connectDB();
|
||||
console.log("🚀 Starting migration: import_home_content...");
|
||||
|
||||
// 2) Load model
|
||||
const Home = require("../models/home");
|
||||
console.log("✅ Home model registered successfully");
|
||||
|
||||
// 3) Load JSON data
|
||||
const dataPath = path.join(__dirname, "..", "data", "home.json");
|
||||
const raw = await fs.readFile(dataPath, "utf8");
|
||||
const homeData = JSON.parse(raw);
|
||||
console.log("📖 Home data loaded from:", dataPath);
|
||||
|
||||
// 4) Clear existing
|
||||
console.log("🧹 Clearing existing Home data...");
|
||||
await Home.deleteMany({});
|
||||
console.log("✅ Existing Home documents cleared");
|
||||
|
||||
// 5) Insert new document
|
||||
const created = await Home.create(homeData);
|
||||
console.log("✅ Home document created with _id:", created._id.toString());
|
||||
|
||||
console.log("🎉 Migration import_home_content completed successfully.");
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error("❌ Migration failed:", err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Migration: Convert About News static items to dynamic Blog selection
|
||||
* Date: 2026-02-07
|
||||
*
|
||||
* This migration:
|
||||
* 1. Adds selectedBlogIds field to About news section
|
||||
* 2. Keeps existing items for backward compatibility
|
||||
* 3. Does NOT delete old data (safe migration)
|
||||
*/
|
||||
|
||||
const mongoose = require("mongoose");
|
||||
require("dotenv").config();
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/SIMS";
|
||||
|
||||
async function up() {
|
||||
try {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
console.log("✓ Connected to MongoDB");
|
||||
|
||||
const AboutUs = mongoose.model("AboutUs", new mongoose.Schema({}, { strict: false }));
|
||||
|
||||
const doc = await AboutUs.findOne();
|
||||
|
||||
if (!doc) {
|
||||
console.log("⚠ No About Us document found. Skipping migration.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already migrated
|
||||
if (doc.news && doc.news.selectedBlogIds !== undefined) {
|
||||
console.log("✓ Migration already applied. Skipping.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add selectedBlogIds field (empty array by default)
|
||||
if (!doc.news) {
|
||||
doc.news = {};
|
||||
}
|
||||
|
||||
doc.news.selectedBlogIds = [];
|
||||
|
||||
// Keep existing items for backward compatibility
|
||||
// Admin can manually select blogs after migration
|
||||
|
||||
await doc.save();
|
||||
|
||||
console.log("✓ Migration completed successfully");
|
||||
console.log(" - Added selectedBlogIds field to About news section");
|
||||
console.log(" - Existing items preserved for backward compatibility");
|
||||
console.log(" - Admin can now select blogs from Blog Management");
|
||||
} catch (error) {
|
||||
console.error("✗ Migration failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function down() {
|
||||
try {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
console.log("✓ Connected to MongoDB");
|
||||
|
||||
const AboutUs = mongoose.model("AboutUs", new mongoose.Schema({}, { strict: false }));
|
||||
|
||||
const doc = await AboutUs.findOne();
|
||||
|
||||
if (!doc || !doc.news) {
|
||||
console.log("⚠ No About Us document found. Skipping rollback.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove selectedBlogIds field
|
||||
if (doc.news.selectedBlogIds !== undefined) {
|
||||
delete doc.news.selectedBlogIds;
|
||||
await doc.save();
|
||||
console.log("✓ Rollback completed - selectedBlogIds removed");
|
||||
} else {
|
||||
console.log("✓ Nothing to rollback");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("✗ Rollback failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration
|
||||
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 };
|
||||
@@ -1,29 +0,0 @@
|
||||
require("dotenv").config();
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
|
||||
console.log("Connected DB");
|
||||
|
||||
const collections = await mongoose.connection.db
|
||||
.listCollections({ name: "auditlogs" })
|
||||
.toArray();
|
||||
|
||||
if (collections.length > 0) {
|
||||
console.log("AuditLog collection already exists");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
await mongoose.connection.createCollection("auditlogs");
|
||||
console.log("AuditLog collection created");
|
||||
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
@@ -1,86 +0,0 @@
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const connectDB = require('../config/database');
|
||||
const About = require('../models/about');
|
||||
|
||||
async function validateAboutData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('About data must be a valid object');
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateAboutData() {
|
||||
try {
|
||||
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'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
|
||||
// Validate
|
||||
await validateAboutData(aboutData);
|
||||
|
||||
// Transform (optional)
|
||||
const finalData = {
|
||||
...aboutData,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
// Insert
|
||||
await About.create(finalData);
|
||||
|
||||
console.log('✓ Migrate About thành công!');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Lỗi:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrateAboutData();
|
||||
@@ -16,10 +16,9 @@ function discoverMigrations() {
|
||||
// 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",
|
||||
"migrate-home.js",
|
||||
"migrate-about.js",
|
||||
|
||||
];
|
||||
|
||||
const migrations = files
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const connectDB = require('../config/database');
|
||||
const Footer = require('../models/footer');
|
||||
|
||||
async function validateFooterData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('Footer data must be a valid object');
|
||||
}
|
||||
|
||||
const required = ['brand', 'explore', 'contact', 'newsletter', 'bottom'];
|
||||
for (const field of required) {
|
||||
if (!data[field]) {
|
||||
throw new Error(`Missing required field: ${field}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateFooterData() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log('Đã kết nối MongoDB...');
|
||||
|
||||
// Xóa dữ liệu cũ
|
||||
await Footer.deleteMany({});
|
||||
console.log('Đã xóa dữ liệu Footer cũ');
|
||||
|
||||
// Đọc file JSON
|
||||
const footerData = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(__dirname, '../data/new/footer.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
|
||||
// Validate
|
||||
await validateFooterData(footerData);
|
||||
|
||||
// Transform
|
||||
const finalData = {
|
||||
...footerData,
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
// Insert
|
||||
await Footer.create(finalData);
|
||||
|
||||
console.log('✓ Migrate Footer thành công!');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Lỗi:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrateFooterData();
|
||||
@@ -1,189 +0,0 @@
|
||||
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();
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
const mongoose = require('mongoose');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const HeaderMenu = require('../models/headerMenu');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/SIMS';
|
||||
|
||||
async function connectDB() {
|
||||
try {
|
||||
await mongoose.connect(MONGODB_URI);
|
||||
console.log('✅ MongoDB Connected for Migration');
|
||||
} catch (err) {
|
||||
console.error('❌ MongoDB Connection Error:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const processMenuItems = async (items, parentId = null) => {
|
||||
for (const item of items) {
|
||||
console.log(` > Importing: ${item.label}`);
|
||||
|
||||
const menuDoc = {
|
||||
title: item.label,
|
||||
slug: item.slug,
|
||||
url: item.href,
|
||||
parentId: parentId,
|
||||
order: item.order || 0,
|
||||
status: item.isActive === false ? "inactive" : "active",
|
||||
type: item.type === "external" ? "external" : "internal"
|
||||
};
|
||||
|
||||
const createdItem = await HeaderMenu.create(menuDoc);
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
await processMenuItems(item.children, createdItem._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function migrate() {
|
||||
await connectDB();
|
||||
|
||||
try {
|
||||
console.log('--- Starting Header Menu Migration ---');
|
||||
|
||||
// 1. Clear existing menu items
|
||||
await HeaderMenu.deleteMany({});
|
||||
console.log('🗑️ Cleared existing HeaderMenu collection');
|
||||
|
||||
// 2. Read JSON data
|
||||
const dataPath = path.join(__dirname, '../data/header-menu.json');
|
||||
const fileData = fs.readFileSync(dataPath, 'utf8');
|
||||
const menuItems = JSON.parse(fileData);
|
||||
|
||||
// 3. Recursive import
|
||||
await processMenuItems(menuItems);
|
||||
|
||||
console.log('--- Migration Completed Successfully ---');
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error('❌ Migration Failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrate();
|
||||
@@ -1,73 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const path = require("path");
|
||||
require("dotenv").config({ path: path.join(__dirname, "../.env") });
|
||||
|
||||
const Header = require("../models/header");
|
||||
const headerData = require("../data/header.json");
|
||||
|
||||
const migrateHeader = async () => {
|
||||
try {
|
||||
const mongoUri = process.env.MONGODB_URI;
|
||||
if (!mongoUri) {
|
||||
throw new Error("MONGODB_URI not found in environment variables");
|
||||
}
|
||||
await mongoose.connect(mongoUri);
|
||||
console.log("Connected to MongoDB");
|
||||
|
||||
// Delete existing header
|
||||
await Header.deleteMany({});
|
||||
console.log("Cleared existing headers");
|
||||
|
||||
// Transform and insert data
|
||||
const headerDocument = {
|
||||
top: {
|
||||
phone: headerData.top?.phone || "",
|
||||
email: headerData.top?.email || "",
|
||||
location: headerData.top?.location || "",
|
||||
socialLinks: (headerData.top?.socialLinks || []).map((link, idx) => ({
|
||||
...link,
|
||||
order: idx,
|
||||
})),
|
||||
languages: headerData.top?.languages || [],
|
||||
},
|
||||
offcanvas: headerData.offcanvas || {},
|
||||
menu: (headerData.menu || []).map((item, idx) => ({
|
||||
...item,
|
||||
order: idx,
|
||||
children:
|
||||
item.children?.map((child, childIdx) => ({
|
||||
...child,
|
||||
order: childIdx,
|
||||
children:
|
||||
child.children?.map((subchild, subIdx) => ({
|
||||
...subchild,
|
||||
order: subIdx,
|
||||
})) || [],
|
||||
})) || [],
|
||||
})),
|
||||
logo: {
|
||||
light: "/assets/img/logo/white-logo.svg",
|
||||
dark: "/assets/img/logo/black-logo.svg",
|
||||
alt: "Hai Learning",
|
||||
},
|
||||
ctaButton: {
|
||||
label: "Get Started",
|
||||
href: "/contact",
|
||||
style: "primary",
|
||||
},
|
||||
status: "active",
|
||||
order: 1,
|
||||
};
|
||||
|
||||
const result = await Header.create(headerDocument);
|
||||
console.log("Header migrated successfully:", result._id);
|
||||
|
||||
await mongoose.connection.close();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Migration error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
migrateHeader();
|
||||
@@ -0,0 +1,56 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const connectDB = require('../config/database');
|
||||
const Home = require('../models/home');
|
||||
|
||||
async function validateHomeData(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
throw new Error('Home data must be a valid object');
|
||||
}
|
||||
|
||||
// Ví dụ validate cơ bản (tuỳ schema bạn chỉnh thêm)
|
||||
if (!data.hero || !data.quickLinks) {
|
||||
throw new Error('Missing required fields: hero or quickLinks');
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateHomeData() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log('Đã kết nối MongoDB...');
|
||||
|
||||
// Xóa dữ liệu cũ
|
||||
await Home.deleteMany({});
|
||||
console.log('Đã xóa dữ liệu Home cũ');
|
||||
|
||||
// Đọc file JSON
|
||||
const homeData = JSON.parse(
|
||||
await fs.readFile(
|
||||
path.join(__dirname, '../data/home.json'),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
|
||||
// Validate
|
||||
await validateHomeData(homeData);
|
||||
|
||||
// Transform nếu cần (optional)
|
||||
const finalData = {
|
||||
...homeData,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
// Insert
|
||||
await Home.create(finalData);
|
||||
|
||||
console.log('✓ Migrate Home thành công!');
|
||||
process.exit(0);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Lỗi:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
migrateHomeData();
|
||||
@@ -1,111 +0,0 @@
|
||||
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]);
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
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 };
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const dotenv = require("dotenv");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const AboutUs = require("../models/aboutUs");
|
||||
|
||||
const migrate = async () => {
|
||||
try {
|
||||
console.log("🚀 Starting About Us migration...");
|
||||
|
||||
// 1. Connect to MongoDB
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
console.log("✅ MongoDB Connected");
|
||||
|
||||
// 2. Read about.json from Backend (Source of Truth)
|
||||
const jsonPath = path.join(__dirname, "../data/about.json");
|
||||
if (!fs.existsSync(jsonPath)) {
|
||||
throw new Error(`Source about.json not found at: ${jsonPath}`);
|
||||
}
|
||||
|
||||
const rawData = fs.readFileSync(jsonPath, "utf8");
|
||||
const jsonData = JSON.parse(rawData);
|
||||
console.log("✅ Read about.json successfully");
|
||||
|
||||
// 3. Delete existing AboutUs documents (Singleton pattern)
|
||||
await AboutUs.deleteMany({});
|
||||
console.log("✅ Cleared existing AboutUs collection");
|
||||
|
||||
// 4. Create new AboutUs document with JSON data
|
||||
const newAboutUs = new AboutUs(jsonData);
|
||||
await newAboutUs.save();
|
||||
console.log("✅ Successfully migrated about.json data to MongoDB");
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Migration failed:", error.message);
|
||||
} finally {
|
||||
// 5. Close connection
|
||||
await mongoose.connection.close();
|
||||
console.log("👋 Database connection closed");
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
migrate();
|
||||
@@ -1,52 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const dotenv = require("dotenv");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const AboutUs = require("../models/aboutUs");
|
||||
|
||||
const seedAbout = async () => {
|
||||
try {
|
||||
console.log("🚀 Starting About section seeding...");
|
||||
|
||||
// 1. Connect to MongoDB
|
||||
if (!process.env.MONGODB_URI) {
|
||||
throw new Error("MONGODB_URI is not defined in environment variables");
|
||||
}
|
||||
await mongoose.connect(process.env.MONGODB_URI);
|
||||
console.log("✅ MongoDB Connected");
|
||||
|
||||
// 2. Read about.json (Single Source of Truth)
|
||||
const jsonPath = path.join(__dirname, "../data/about.json");
|
||||
if (!fs.existsSync(jsonPath)) {
|
||||
throw new Error(`Source about.json not found at: ${jsonPath}`);
|
||||
}
|
||||
|
||||
const rawData = fs.readFileSync(jsonPath, "utf8");
|
||||
const jsonData = JSON.parse(rawData);
|
||||
console.log("✅ Read data/about.json successfully");
|
||||
|
||||
// 3. Upsert logic (Singleton pattern)
|
||||
// We look for any existing document and update it, or create a new one if none exists.
|
||||
await AboutUs.findOneAndUpdate(
|
||||
{},
|
||||
jsonData,
|
||||
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
||||
);
|
||||
|
||||
console.log("✅ Successfully seeded about.json data to MongoDB (Upserted)");
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Seeding failed:", error.message);
|
||||
} finally {
|
||||
// 4. Close connection
|
||||
await mongoose.connection.close();
|
||||
console.log("👋 Database connection closed");
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
seedAbout();
|
||||
@@ -1,106 +0,0 @@
|
||||
const mongoose = require("mongoose");
|
||||
const path = require("path");
|
||||
require("dotenv").config({ path: path.join(__dirname, "../.env") });
|
||||
|
||||
const Header = require("../models/header");
|
||||
|
||||
async function updateHeaderData() {
|
||||
try {
|
||||
// Connect to MongoDB
|
||||
await mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost:27017/hailearning");
|
||||
console.log("Connected to MongoDB");
|
||||
|
||||
// Find the first header
|
||||
let header = await Header.findOne().sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
console.log("No header found, creating new one...");
|
||||
header = new Header({
|
||||
top: {
|
||||
phone: "+09 378 357 5222",
|
||||
email: "info@hailearning.edu.vn",
|
||||
location: "69 Street, 5th Avenue LA, United States",
|
||||
socialLinks: [
|
||||
{
|
||||
platform: "linkedin",
|
||||
url: "https://linkedin.com",
|
||||
icon: "fa-brands fa-linkedin",
|
||||
},
|
||||
{
|
||||
platform: "twitter",
|
||||
url: "https://twitter.com",
|
||||
icon: "fa-brands fa-twitter",
|
||||
},
|
||||
{
|
||||
platform: "instagram",
|
||||
url: "https://instagram.com",
|
||||
icon: "fa-brands fa-instagram",
|
||||
},
|
||||
{
|
||||
platform: "youtube",
|
||||
url: "https://youtube.com",
|
||||
icon: "fa-brands fa-youtube",
|
||||
},
|
||||
],
|
||||
languages: [
|
||||
{ name: "English", value: "1" },
|
||||
{ name: "Bangla", value: "2" },
|
||||
{ name: "Hindi", value: "3" },
|
||||
],
|
||||
},
|
||||
status: "active",
|
||||
order: 1,
|
||||
});
|
||||
} else {
|
||||
console.log("Header found, updating...");
|
||||
// Update existing header
|
||||
header.top = {
|
||||
phone: header.top?.phone || "+09 378 357 5222",
|
||||
email: header.top?.email || "info@hailearning.edu.vn",
|
||||
location: header.top?.location || "69 Street, 5th Avenue LA, United States",
|
||||
socialLinks:
|
||||
header.top?.socialLinks?.length > 0
|
||||
? header.top.socialLinks
|
||||
: [
|
||||
{
|
||||
platform: "linkedin",
|
||||
url: "https://linkedin.com",
|
||||
icon: "fa-brands fa-linkedin",
|
||||
},
|
||||
{
|
||||
platform: "twitter",
|
||||
url: "https://twitter.com",
|
||||
icon: "fa-brands fa-twitter",
|
||||
},
|
||||
{
|
||||
platform: "instagram",
|
||||
url: "https://instagram.com",
|
||||
icon: "fa-brands fa-instagram",
|
||||
},
|
||||
{
|
||||
platform: "youtube",
|
||||
url: "https://youtube.com",
|
||||
icon: "fa-brands fa-youtube",
|
||||
},
|
||||
],
|
||||
languages: header.top?.languages || [
|
||||
{ name: "English", value: "1" },
|
||||
{ name: "Bangla", value: "2" },
|
||||
{ name: "Hindi", value: "3" },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
await header.save();
|
||||
console.log("Header updated successfully!");
|
||||
console.log("Header data:", JSON.stringify(header, null, 2));
|
||||
|
||||
await mongoose.connection.close();
|
||||
console.log("Database connection closed");
|
||||
} catch (error) {
|
||||
console.error("Error updating header:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
updateHeaderData();
|
||||
Reference in New Issue
Block a user