Monorepo for Aesthetic.Computer aesthetic.computer
at main 149 lines 5.4 kB view raw
1#!/usr/bin/env node 2// generate-user-codes.mjs 3// Retroactively add permanent user code to all existing users 4 5import { config } from 'dotenv'; 6import { fileURLToPath } from 'url'; 7import { dirname, join } from 'path'; 8import { connect } from '../../../system/backend/database.mjs'; 9import { shell } from '../../../system/backend/shell.mjs'; 10import { generateUniqueUserCode, ensureUserCodeIndex } from '../../../system/public/aesthetic.computer/lib/user-code.mjs'; 11 12// Load environment from vault 13const __filename = fileURLToPath(import.meta.url); 14const __dirname = dirname(__filename); 15const vaultEnvPath = join(__dirname, '../../../aesthetic-computer-vault/at/.env'); 16config({ path: vaultEnvPath }); 17 18async function main() { 19 // Check for --dry-run flag 20 const isDryRun = process.argv.includes('--dry-run'); 21 22 if (isDryRun) { 23 console.log('🔍 DRY RUN MODE - No database changes will be made\n'); 24 } 25 26 console.log('🔑 Generating permanent user codes for all AC users...\n'); 27 28 try { 29 const database = await connect(); 30 const handles = database.db.collection('@handles'); 31 const verifications = database.db.collection('verifications'); 32 33 // Ensure unique index exists (skip in dry-run) 34 if (!isDryRun) { 35 await ensureUserCodeIndex(database); 36 } 37 38 // Strategy: Find all verified users, then check which ones need codes 39 // This catches both: 40 // 1. Users in @handles without codes (handled users) 41 // 2. Users in verifications but NOT in @handles (unhandled users) 42 43 const allVerifiedUsers = await verifications.find({}).toArray(); 44 console.log(`📊 Found ${allVerifiedUsers.length} total verified users`); 45 46 const usersNeedingCodes = []; 47 48 for (const verified of allVerifiedUsers) { 49 const userId = verified._id; 50 const handleRecord = await handles.findOne({ _id: userId }); 51 52 // User needs a code if: 53 // - They have no @handles record at all, OR 54 // - They have a @handles record but no code field 55 if (!handleRecord || !handleRecord.code) { 56 usersNeedingCodes.push({ 57 _id: userId, 58 handle: handleRecord?.handle, 59 created_at: handleRecord?.created_at || handleRecord?.createdAt, 60 existsInHandles: !!handleRecord 61 }); 62 } 63 } 64 65 console.log(`📊 Found ${usersNeedingCodes.length} users without codes`); 66 console.log(` - ${usersNeedingCodes.filter(u => u.handle).length} with handles`); 67 console.log(` - ${usersNeedingCodes.filter(u => !u.handle).length} without handles\n`); 68 69 if (usersNeedingCodes.length === 0) { 70 console.log('✅ All users already have codes!'); 71 await database.disconnect(); 72 return; 73 } 74 75 let successCount = 0; 76 let errorCount = 0; 77 78 for (const user of usersNeedingCodes) { 79 try { 80 // Determine creation date (use existing field or default to now) 81 const createdAt = user.created_at || 82 user.createdAt || 83 user._id.getTimestamp?.() || // Extract from ObjectId if available 84 new Date(); // Fallback to now 85 86 // Generate unique code based on creation year 87 const userCode = await generateUniqueUserCode(database, createdAt); 88 89 // Update or insert user record (skip in dry-run) 90 if (!isDryRun) { 91 if (user.existsInHandles) { 92 // User already in @handles, just add code 93 await handles.updateOne( 94 { _id: user._id }, 95 { 96 $set: { 97 code: userCode, 98 code_created_at: new Date() 99 } 100 } 101 ); 102 } else { 103 // User not in @handles yet, create entry with code 104 await handles.insertOne({ 105 _id: user._id, 106 code: userCode, 107 code_created_at: new Date(), 108 created_at: createdAt 109 }); 110 } 111 } 112 113 const handle = user.handle ? `@${user.handle}` : '(no handle)'; 114 const year = createdAt.getFullYear(); 115 const prefix = isDryRun ? '🔍' : '✅'; 116 const action = user.existsInHandles ? 'updated' : 'created'; 117 console.log(`${prefix} ${userCode}${handle} (${user._id}) [${year}] [${action}]`); 118 successCount++; 119 120 } catch (error) { 121 console.error(`❌ Failed for ${user._id}:`, error.message); 122 errorCount++; 123 } 124 } 125 126 console.log('\n═══════════════════════════════════════════'); 127 if (isDryRun) { 128 console.log('🔍 DRY RUN COMPLETE - No changes written to database'); 129 } 130 console.log(`✅ Success: ${successCount}`); 131 if (errorCount > 0) { 132 console.log(`❌ Errors: ${errorCount}`); 133 } 134 console.log('═══════════════════════════════════════════\n'); 135 136 if (isDryRun) { 137 console.log('To apply these changes, run without --dry-run flag:\n'); 138 console.log(' node generate-user-codes.mjs\n'); 139 } 140 141 await database.disconnect(); 142 143 } catch (error) { 144 console.error('❌ Migration failed:', error); 145 process.exit(1); 146 } 147} 148 149main();