Merge branch 'develop' into fea/dat-20042026-CMS-Partnerships-Accreditation-History-Admissions-Policies

This commit is contained in:
Tống Thành Đạt
2026-04-22 18:08:53 +07:00
80 changed files with 3334 additions and 19058 deletions
-38
View File
@@ -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 };
-186
View File
@@ -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,
};
-182
View File
@@ -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 };
-336
View File
@@ -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();
-71
View File
@@ -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();
-68
View File
@@ -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;
-47
View File
@@ -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 };
-29
View File
@@ -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();
-86
View File
@@ -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);
+60
View File
@@ -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();
+3 -4
View File
@@ -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
+58
View File
@@ -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/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();
-189
View File
@@ -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();
-69
View File
@@ -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();
-73
View File
@@ -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();
+56
View File
@@ -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();
-111
View File
@@ -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]);
}
-120
View File
@@ -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 };
-48
View File
@@ -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();
-52
View File
@@ -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();
+190
View File
@@ -0,0 +1,190 @@
require('dotenv').config();
const mongoose = require('mongoose');
const slugify = require('slugify');
// Data from lams/app/components/layout/Header/header.json
const headerJsonData = {
"logo": {
"image": "/uploads/header/logo.jp",
"href": "/"
},
"navLinks": [
{
"label": "About Us",
"href": "/about",
"children": [
{
"label": "History & Milestones",
"href": "/about/history"
},
{
"label": "Accreditation",
"href": "/about/accreditation"
},
{
"label": "Partnerships",
"href": "/about/partnerships"
}
]
},
{
"label": "Programs",
"href": "/programmes"
},
{
"label": "Student Support",
"href": "/student-support"
},
{
"label": "Admissions & Tuition",
"href": "/admissions"
},
{
"label": "Blog",
"href": "/blog"
},
{
"label": "Contact",
"href": "/contact"
}
],
"actions": {
"signIn": {
"label": "Sign In",
"href": "/signin"
},
"cta": {
"label": "Request Info",
"href": "/request"
}
}
};
async function seedHeader() {
try {
console.log('=== Seed Header Data from JSON ===');
console.log('Connecting to MongoDB...');
await mongoose.connect(process.env.MONGODB_URI);
console.log('✓ Connected to MongoDB');
const Header = require('../models/header');
const HeaderMenu = require('../models/headerMenu');
// Step 1: Seed Header document
console.log('\nStep 1: Seeding Header document...');
const existingHeader = await Header.findOne();
if (existingHeader) {
console.log('⚠ Header document already exists. Updating...');
existingHeader.logo = {
light: headerJsonData.logo.image,
dark: '',
alt: 'LAMS Logo',
};
existingHeader.signInButton = {
label: headerJsonData.actions.signIn.label,
href: headerJsonData.actions.signIn.href,
};
existingHeader.ctaButton = {
label: headerJsonData.actions.cta.label,
href: headerJsonData.actions.cta.href,
style: 'primary',
};
existingHeader.status = 'active';
await existingHeader.save();
console.log('✓ Header document updated');
} else {
const header = new Header({
logo: {
light: headerJsonData.logo.image,
dark: '',
alt: 'LAMS Logo',
},
signInButton: {
label: headerJsonData.actions.signIn.label,
href: headerJsonData.actions.signIn.href,
},
ctaButton: {
label: headerJsonData.actions.cta.label,
href: headerJsonData.actions.cta.href,
style: 'primary',
},
status: 'active',
order: 1,
});
await header.save();
console.log('✓ Header document created');
}
// Step 2: Seed HeaderMenu documents
console.log('\nStep 2: Seeding HeaderMenu documents...');
const existingMenuCount = await HeaderMenu.countDocuments();
if (existingMenuCount > 0) {
console.log(`⚠ Found ${existingMenuCount} existing menu items. Skipping menu seed.`);
console.log(' To re-seed menu, delete existing menu items first.');
} else {
let order = 0;
for (const navLink of headerJsonData.navLinks) {
// Create parent menu item
const parentSlug = slugify(navLink.label, { lower: true, strict: true });
const parentMenu = new HeaderMenu({
title: navLink.label,
slug: parentSlug,
url: navLink.href,
parentId: null,
order: order++,
status: 'active',
type: 'internal',
});
await parentMenu.save();
console.log(` ✓ Created parent menu: ${navLink.label}`);
// Create children menu items if they exist
if (navLink.children && navLink.children.length > 0) {
let childOrder = 0;
for (const child of navLink.children) {
const childSlug = slugify(child.label, { lower: true, strict: true });
const childMenu = new HeaderMenu({
title: child.label,
slug: childSlug,
url: child.href,
parentId: parentMenu._id,
order: childOrder++,
status: 'active',
type: 'internal',
});
await childMenu.save();
console.log(` ✓ Created child menu: ${child.label}`);
}
}
}
console.log(`✓ Created ${order} parent menu items with their children`);
}
console.log('\n=== Seed Complete ===');
await mongoose.disconnect();
console.log('✓ Disconnected from MongoDB');
} catch (error) {
console.error('✗ Seed failed:', error);
process.exit(1);
}
}
// Run seed if this script is executed directly
if (require.main === module) {
seedHeader()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
}
module.exports = seedHeader;
-106
View File
@@ -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();