Files
cms.uldp.edu.vn/scripts/2025_12_10_125800_activities.js
r2xrzh9q2z-lab d1b931d547 first commit
2026-02-02 11:07:09 +07:00

220 lines
7.7 KiB
JavaScript

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};