agmission/Development/server/scripts/migratePartnerSystemUserCustomerToParent.js

319 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
'use strict';
/**
* Migration Script: Migrate PartnerSystemUser 'customer' field to 'parent'
*
* This script migrates existing PartnerSystemUser documents that have a 'customer' field
* but no 'parent' field. It copies the customer value to parent and optionally removes
* the customer field.
*
* Usage:
* node scripts/migratePartnerSystemUserCustomerToParent.js [options]
*
* Options:
* --preview Show what will be migrated without making changes
* --remove-old Remove the 'customer' field after migration (default: keep for safety)
* --env <path> Path to environment file (default: ../environment.env)
* --help Show this help message
*
* Examples:
* # Preview migration (recommended first)
* node scripts/migratePartnerSystemUserCustomerToParent.js --preview
*
* # Execute migration (keep customer field for safety)
* node scripts/migratePartnerSystemUserCustomerToParent.js
*
* # Execute migration and remove customer field
* node scripts/migratePartnerSystemUserCustomerToParent.js --remove-old
*
* # Use production environment
* node scripts/migratePartnerSystemUserCustomerToParent.js --env ../environment_prod.env --preview
*/
const path = require('path');
// Parse command line arguments
const args = process.argv.slice(2);
const options = {
preview: args.includes('--preview'),
removeOld: args.includes('--remove-old'),
envPath: '../environment.env',
help: args.includes('--help') || args.includes('-h')
};
// Parse --env option
const envIndex = args.indexOf('--env');
if (envIndex !== -1 && args[envIndex + 1]) {
options.envPath = args[envIndex + 1];
}
if (options.help) {
console.log(`
Migration Script: Migrate PartnerSystemUser 'customer' field to 'parent'
Usage:
node scripts/migratePartnerSystemUserCustomerToParent.js [options]
Options:
--preview Show what will be migrated without making changes
--remove-old Remove the 'customer' field after migration (default: keep for safety)
--env <path> Path to environment file (default: ../environment.env)
--help Show this help message
Examples:
# Preview migration (recommended first)
node scripts/migratePartnerSystemUserCustomerToParent.js --preview
# Execute migration (keep customer field for safety)
node scripts/migratePartnerSystemUserCustomerToParent.js
# Execute migration and remove customer field
node scripts/migratePartnerSystemUserCustomerToParent.js --remove-old
`);
process.exit(0);
}
// Load environment
const envPath = path.isAbsolute(options.envPath) ? options.envPath : path.join(process.cwd(), options.envPath);
require('dotenv').config({ path: envPath });
const mongoose = require('mongoose');
const { DBConnection } = require('../helpers/db/connect');
const { UserTypes } = require('../helpers/constants');
// Create database connection instance
const dbConn = new DBConnection('Partner System User Migration');
// Get raw collection access (bypass mongoose schema)
async function getRawCollection() {
return mongoose.connection.collection('users');
}
async function migratePartnerSystemUsers() {
console.log('================================================================================');
console.log('PARTNER SYSTEM USER MIGRATION: customer → parent');
console.log('================================================================================');
console.log(`Mode: ${options.preview ? 'PREVIEW (no changes)' : 'EXECUTE'}`);
console.log(`Remove old customer field: ${options.removeOld ? 'YES' : 'NO'}`);
console.log(`Environment: ${options.envPath}`);
console.log('================================================================================\n');
try {
// Connect to database
console.log('Connecting to database...');
await dbConn.connect({ exitOnError: false });
console.log('Connected successfully.\n');
const usersCollection = await getRawCollection();
// Find all PartnerSystemUser documents that need migration
// Case 1: Has 'customer' but no 'parent'
// Case 2: Has 'customer' and 'parent' but they differ (data inconsistency)
const query = {
kind: UserTypes.PARTNER_SYSTEM_USER,
customer: { $exists: true }
};
const usersToMigrate = await usersCollection.find(query).toArray();
console.log(`Found ${usersToMigrate.length} PartnerSystemUser document(s) with 'customer' field.\n`);
if (usersToMigrate.length === 0) {
console.log('✅ No migration needed - all documents are up to date.');
return { migrated: 0, skipped: 0, errors: [] };
}
const stats = {
migrated: 0,
skipped: 0,
alreadyCorrect: 0,
errors: []
};
console.log('Processing documents:\n');
for (const user of usersToMigrate) {
const customerId = user.customer;
const parentId = user.parent;
const username = user.username || user.name || user._id;
// Check if parent already matches customer
if (parentId && parentId.toString() === customerId.toString()) {
console.log(` ⏭️ ${username}: parent already equals customer - ${options.removeOld ? 'will remove customer field' : 'skipping'}`);
stats.alreadyCorrect++;
if (!options.preview && options.removeOld) {
// Remove the redundant customer field
await usersCollection.updateOne(
{ _id: user._id },
{ $unset: { customer: '' } }
);
stats.migrated++;
} else {
stats.skipped++;
}
continue;
}
// Check for data inconsistency (parent exists but differs from customer)
if (parentId && parentId.toString() !== customerId.toString()) {
console.log(` ⚠️ ${username}: INCONSISTENCY - parent (${parentId}) differs from customer (${customerId})`);
console.log(` → Will use customer value for parent`);
}
console.log(` 📝 ${username}:`);
console.log(` customer: ${customerId}`);
console.log(` parent: ${parentId || '(not set)'}${customerId}`);
if (!options.preview) {
try {
const updateOps = {
$set: { parent: customerId }
};
if (options.removeOld) {
updateOps.$unset = { customer: '' };
}
const result = await usersCollection.updateOne(
{ _id: user._id },
updateOps
);
if (result.modifiedCount === 1) {
console.log(` ✅ Migrated successfully`);
stats.migrated++;
} else {
console.log(` ⚠️ No changes made (modifiedCount: ${result.modifiedCount})`);
stats.skipped++;
}
} catch (error) {
console.log(` ❌ Error: ${error.message}`);
stats.errors.push({ userId: user._id, username, error: error.message });
}
} else {
stats.migrated++; // Count as would-be-migrated in preview
}
}
console.log('\n================================================================================');
console.log('MIGRATION SUMMARY');
console.log('================================================================================');
console.log(`Total documents found: ${usersToMigrate.length}`);
console.log(`Already correct: ${stats.alreadyCorrect}`);
if (options.preview) {
console.log(`Would migrate: ${stats.migrated}`);
console.log(`Would skip: ${stats.skipped}`);
} else {
console.log(`Successfully migrated: ${stats.migrated}`);
console.log(`Skipped: ${stats.skipped}`);
}
console.log(`Errors: ${stats.errors.length}`);
if (stats.errors.length > 0) {
console.log('\nErrors:');
stats.errors.forEach(err => {
console.log(` - ${err.username} (${err.userId}): ${err.error}`);
});
}
if (options.preview) {
console.log('\n⚠ PREVIEW MODE - No changes were made.');
console.log('Run without --preview to execute the migration.');
} else {
console.log('\n✅ Migration completed.');
if (!options.removeOld) {
console.log('Note: The "customer" field was kept for safety.');
console.log('Run with --remove-old to remove it in a future migration.');
}
}
return stats;
} catch (error) {
console.error('\n❌ Migration failed:', error.message);
throw error;
}
}
// Verification function to check migration results
async function verifyMigration() {
console.log('\n================================================================================');
console.log('VERIFICATION');
console.log('================================================================================\n');
try {
const usersCollection = await getRawCollection();
// Check for documents with customer but no parent
const missingParent = await usersCollection.countDocuments({
kind: UserTypes.PARTNER_SYSTEM_USER,
customer: { $exists: true },
parent: { $exists: false }
});
// Check for documents with parent set
const hasParent = await usersCollection.countDocuments({
kind: UserTypes.PARTNER_SYSTEM_USER,
parent: { $exists: true }
});
// Check for documents with both fields matching
const pipeline = [
{
$match: {
kind: UserTypes.PARTNER_SYSTEM_USER,
parent: { $exists: true },
customer: { $exists: true }
}
},
{
$project: {
match: { $eq: ['$parent', '$customer'] }
}
},
{
$match: { match: true }
},
{
$count: 'count'
}
];
const matchingResult = await usersCollection.aggregate(pipeline).toArray();
const matchingCount = matchingResult[0]?.count || 0;
console.log('Current state of PartnerSystemUser documents:');
console.log(` With parent field set: ${hasParent}`);
console.log(` With customer but missing parent: ${missingParent}`);
console.log(` With both fields matching: ${matchingCount}`);
if (missingParent === 0 && hasParent > 0) {
console.log('\n✅ All PartnerSystemUser documents have parent field set.');
} else if (missingParent > 0) {
console.log(`\n⚠️ ${missingParent} document(s) still need migration.`);
}
} catch (error) {
console.error('Verification failed:', error.message);
}
}
// Main execution
async function main() {
try {
await migratePartnerSystemUsers();
await verifyMigration();
} catch (error) {
console.error('Migration script failed:', error);
process.exit(1);
} finally {
await mongoose.connection.close();
console.log('\nDatabase connection closed.');
process.exit(0);
}
}
main();