forked from UKSOURCE/cms.hailearning.edu.vn
first commit
This commit is contained in:
219
scripts/2025_12_10_125800_activities.js
Normal file
219
scripts/2025_12_10_125800_activities.js
Normal file
@@ -0,0 +1,219 @@
|
||||
require("dotenv").config();
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const connectDB = require("../config/database");
|
||||
const Activity = require("../models/activity");
|
||||
|
||||
/**
|
||||
* Transform activities.json data to match Activity model schema
|
||||
*/
|
||||
function transformActivity(source, index, heroData) {
|
||||
// Return a document that preserves the main activity fields and also
|
||||
// keeps the detailed camp information (from `camp-detail`) under the
|
||||
// `campDetail` key so it can be queried later.
|
||||
return {
|
||||
// Add hero section from global hero data if available (support activities/booking variants)
|
||||
hero: heroData && Array.isArray(heroData) && heroData.length > 0 ? {
|
||||
titleActivities: heroData[0].titleActivities || heroData[0].title || "",
|
||||
titleBooking: heroData[0].titleBooking || heroData[0].title || "",
|
||||
bannerImageActivities: heroData[0].bannerImageActivities || heroData[0].bannerImage || "",
|
||||
bannerImageBooking: heroData[0].bannerImageBooking || heroData[0].bannerImage || "",
|
||||
} : {
|
||||
titleActivities: "",
|
||||
titleBooking: "",
|
||||
bannerImageActivities: "",
|
||||
bannerImageBooking: "",
|
||||
},
|
||||
name: source.name || "",
|
||||
price: source.price || 0,
|
||||
priceText: source.priceText || `from ${source.price || 0} USD`,
|
||||
season: Array.isArray(source.season) ? source.season : [],
|
||||
age:
|
||||
Array.isArray(source.age) && source.age.length === 2
|
||||
? source.age
|
||||
: [12, 18],
|
||||
locations: Array.isArray(source.locations) ? source.locations : [],
|
||||
image: source.image || "",
|
||||
link: source.link || "",
|
||||
program: source.program || "",
|
||||
rating: source.rating || 4,
|
||||
isActive: typeof source.isActive === 'boolean' ? source.isActive : true,
|
||||
order: typeof source.order === 'number' ? source.order : index,
|
||||
// Keep the rich camp detail under a schema-friendly key
|
||||
campDetail: source['camp-detail'] || source.campDetail || {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migration: activities
|
||||
* Import activities from data/activities.json into MongoDB
|
||||
*/
|
||||
async function migrate() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log("Starting migration: activities...");
|
||||
|
||||
// Read data file
|
||||
const dataPath = path.join(__dirname, "../data/activities.json");
|
||||
console.log(`Reading data from ${dataPath}...`);
|
||||
|
||||
// Use fs.existsSync and fs.readFileSync for synchronous check and read
|
||||
const fsSync = require("fs");
|
||||
if (!fsSync.existsSync(dataPath)) {
|
||||
throw new Error("Data file not found!");
|
||||
}
|
||||
|
||||
const rawData = fsSync.readFileSync(dataPath, "utf8");
|
||||
const data = JSON.parse(rawData);
|
||||
|
||||
// Handle new data structure
|
||||
const activitiesData = Array.isArray(data) ? data : data.camps || [];
|
||||
const filtersData = Array.isArray(data) ? [] : data.filter || [];
|
||||
const heroData = Array.isArray(data) ? null : data.hero || null;
|
||||
|
||||
console.log(
|
||||
`Found ${activitiesData.length} activities and ${filtersData.length} filter groups to migrate.`
|
||||
);
|
||||
|
||||
// --- Migrate Activities ---
|
||||
if (activitiesData.length > 0) {
|
||||
console.log("Migrating activities...");
|
||||
|
||||
// Transform data if needed (using the existing transformActivity for consistency, or a new one if structure changed)
|
||||
const activitiesToInsert = activitiesData.map(
|
||||
(source, index) => transformActivity(source, index, heroData) // Pass heroData to transform function
|
||||
);
|
||||
|
||||
const insertedActivities = await Activity.insertMany(activitiesToInsert, {
|
||||
ordered: false,
|
||||
});
|
||||
console.log(`Inserted ${insertedActivities.length} activities.`);
|
||||
} else {
|
||||
console.log("No activities to migrate.");
|
||||
}
|
||||
|
||||
// --- Migrate Filters ---
|
||||
if (filtersData.length > 0) {
|
||||
console.log("Migrating activity filters...");
|
||||
|
||||
// Deduplicate filters by value (value must be unique per model)
|
||||
const seen = new Map();
|
||||
const filtersToUpsert = [];
|
||||
filtersData.forEach((item, index) => {
|
||||
// sanitize incoming filter items (remove any unexpected keys such as `count`)
|
||||
const sanitizeItems = (arr) =>
|
||||
(Array.isArray(arr) ? arr : [])
|
||||
.map((it) => ({
|
||||
value: (it && it.value) ? it.value.toString().trim() : "",
|
||||
label: (it && it.label) ? it.label.toString().trim() : "",
|
||||
}))
|
||||
.filter((it) => it.value && it.label);
|
||||
|
||||
const f = {
|
||||
label: item.label || item.name || `Filter ${index + 1}`,
|
||||
value: (item.value || (item.label || item.name || `filter-${index + 1}`))
|
||||
.toString()
|
||||
.trim(),
|
||||
items: sanitizeItems(item.items),
|
||||
order: item.order || index + 1,
|
||||
};
|
||||
|
||||
if (!f.value) return; // skip invalid
|
||||
|
||||
if (seen.has(f.value)) {
|
||||
// merge items if duplicate in source (merge by `value`, prefer first occurrence)
|
||||
const existing = seen.get(f.value);
|
||||
const mergedMap = new Map();
|
||||
[...existing.items, ...f.items].forEach((it) => {
|
||||
if (it && it.value) mergedMap.set(it.value, it);
|
||||
});
|
||||
existing.items = Array.from(mergedMap.values());
|
||||
existing.order = Math.min(existing.order, f.order);
|
||||
} else {
|
||||
seen.set(f.value, f);
|
||||
filtersToUpsert.push(f);
|
||||
}
|
||||
});
|
||||
|
||||
if (filtersToUpsert.length === 0) {
|
||||
console.log("No valid activity filters to migrate after dedupe.");
|
||||
} else {
|
||||
// Use bulkWrite with upsert to avoid duplicate-key errors and to update existing docs
|
||||
const bulkOps = filtersToUpsert.map((f) => ({
|
||||
updateOne: {
|
||||
filter: { value: f.value },
|
||||
update: { $set: { label: f.label, items: f.items, order: f.order } },
|
||||
upsert: true,
|
||||
},
|
||||
}));
|
||||
|
||||
// Upsert the consolidated filters into a single Activity document
|
||||
// that is used to store global filter definitions (marked by isFiltersDoc: true)
|
||||
const upsertResult = await Activity.findOneAndUpdate(
|
||||
{ isFiltersDoc: true },
|
||||
{ $set: { filters: filtersToUpsert, isFiltersDoc: true } },
|
||||
{ upsert: true, new: true }
|
||||
);
|
||||
console.log(`Upserted filters into Activity document id=${upsertResult._id} groups=${(upsertResult.filters || []).length}`);
|
||||
}
|
||||
} else {
|
||||
console.log("No activity filters to migrate.");
|
||||
}
|
||||
|
||||
console.log("Migration activities completed successfully!");
|
||||
|
||||
const mongoose = require("mongoose");
|
||||
await mongoose.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Migration error:", error);
|
||||
|
||||
// If some documents failed but others succeeded, log partial success
|
||||
if (error.insertedDocs && error.insertedDocs.length > 0) {
|
||||
console.log(
|
||||
`Partial success: ${error.insertedDocs.length} documents inserted`
|
||||
);
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback: Delete all activities (use with caution!)
|
||||
*/
|
||||
async function rollback() {
|
||||
try {
|
||||
await connectDB();
|
||||
console.log("Starting rollback...");
|
||||
|
||||
const actResult = await Activity.deleteMany({});
|
||||
console.log(`✅ Deleted ${actResult.deletedCount} activities`);
|
||||
|
||||
// Remove any filters document stored as an Activity with isFiltersDoc=true
|
||||
const filterResult = await Activity.deleteMany({ isFiltersDoc: true });
|
||||
console.log(`✅ Deleted ${filterResult.deletedCount} activity filters documents`);
|
||||
|
||||
console.log("Rollback completed successfully!");
|
||||
|
||||
const mongoose = require("mongoose");
|
||||
await mongoose.disconnect();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Rollback error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run migration or rollback based on command line arguments
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes("--rollback")) {
|
||||
rollback();
|
||||
} else {
|
||||
migrate();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {migrate, rollback};
|
||||
Reference in New Issue
Block a user