#!/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 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 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();