Monorepo for Aesthetic.Computer
aesthetic.computer
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();