Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 250 lines 7.5 kB view raw
1// Migration script to add short codes to existing paintings 2// Run with: npm run migrate:dry (or migrate, migrate:user, migrate:guest) 3 4import { connect } from "../system/backend/database.mjs"; 5import { generateUniqueCode } from "../system/backend/painting-codes/generate-code.mjs"; 6import { codeExists } from "../system/backend/painting-codes/lookup.mjs"; 7import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3"; 8 9const args = process.argv.slice(2); 10const dryRun = args.includes("--dry-run"); 11const userOnly = args.includes("--user-only"); 12const guestOnly = args.includes("--guest-only"); 13 14console.log("🎨 Painting Short Code Migration"); 15console.log("================================\n"); 16 17if (dryRun) { 18 console.log("🔍 DRY RUN MODE - No changes will be made\n"); 19} 20 21// S3 Client setup 22const s3Client = new S3Client({ 23 region: process.env.DO_SPACES_REGION || "sfo3", 24 endpoint: process.env.DO_SPACES_ENDPOINT || "https://sfo3.digitaloceanspaces.com", 25 credentials: { 26 accessKeyId: process.env.DO_SPACES_KEY || process.env.ART_KEY, 27 secretAccessKey: process.env.DO_SPACES_SECRET || process.env.ART_SECRET, 28 }, 29}); 30 31async function listGuestPaintings() { 32 const bucket = process.env.ART_SPACE_NAME || "art-aesthetic-computer"; 33 const paintings = []; 34 let continuationToken; 35 36 console.log(`📦 Listing guest paintings from bucket: ${bucket}`); 37 38 do { 39 const command = new ListObjectsV2Command({ 40 Bucket: bucket, 41 ContinuationToken: continuationToken, 42 }); 43 44 const response = await s3Client.send(command); 45 46 if (response.Contents) { 47 for (const item of response.Contents) { 48 if (item.Key.endsWith(".png")) { 49 paintings.push({ 50 key: item.Key, 51 slug: item.Key.replace(".png", ""), 52 lastModified: item.LastModified, 53 size: item.Size, 54 }); 55 } 56 } 57 } 58 59 continuationToken = response.NextContinuationToken; 60 } while (continuationToken); 61 62 console.log(` Found ${paintings.length} guest paintings\n`); 63 return paintings; 64} 65 66async function migrateUserPaintings(database) { 67 if (guestOnly) { 68 console.log("⏭️ Skipping user paintings (--guest-only flag)\n"); 69 return { processed: 0, updated: 0, skipped: 0 }; 70 } 71 72 console.log("👤 Migrating USER paintings"); 73 console.log("---------------------------"); 74 75 const paintings = database.db.collection("paintings"); 76 77 // Find all user paintings without codes 78 const userPaintings = await paintings 79 .find({ 80 user: { $exists: true }, 81 code: { $exists: false }, 82 }) 83 .toArray(); 84 85 console.log(` Found ${userPaintings.length} user paintings without codes`); 86 87 let processed = 0; 88 let updated = 0; 89 let skipped = 0; 90 91 for (const painting of userPaintings) { 92 processed++; 93 94 try { 95 // Generate unique code 96 const code = await generateUniqueCode(codeExists); 97 98 if (!dryRun) { 99 await paintings.updateOne( 100 { _id: painting._id }, 101 { 102 $set: { 103 code, 104 bucket: "user-aesthetic-computer", 105 type: "user", 106 }, 107 } 108 ); 109 updated++; 110 } 111 112 if (processed % 100 === 0 || dryRun) { 113 console.log(` [${processed}/${userPaintings.length}] ${dryRun ? "Would assign" : "Assigned"} code: ${code}${painting.slug}`); 114 } 115 } catch (error) { 116 console.error(` ❌ Error processing ${painting.slug}:`, error.message); 117 skipped++; 118 } 119 } 120 121 console.log(`\n ✅ Processed: ${processed}`); 122 console.log(` ${dryRun ? "📝 Would update" : "💾 Updated"}: ${updated}`); 123 if (skipped > 0) console.log(` ⚠️ Skipped: ${skipped}`); 124 console.log(); 125 126 return { processed, updated, skipped }; 127} 128 129async function migrateGuestPaintings(database) { 130 if (userOnly) { 131 console.log("⏭️ Skipping guest paintings (--user-only flag)\n"); 132 return { processed: 0, created: 0, skipped: 0 }; 133 } 134 135 console.log("🎭 Migrating GUEST paintings"); 136 console.log("----------------------------"); 137 138 const paintings = database.db.collection("paintings"); 139 140 // Get list of guest paintings from S3 141 const s3Paintings = await listGuestPaintings(); 142 143 let processed = 0; 144 let created = 0; 145 let skipped = 0; 146 147 for (const s3Painting of s3Paintings) { 148 processed++; 149 150 try { 151 // Check if painting already exists in database 152 const existing = await paintings.findOne({ slug: s3Painting.slug }); 153 154 if (existing) { 155 // Already has a database record 156 if (!existing.code) { 157 // Needs a code 158 const code = await generateUniqueCode(codeExists); 159 160 if (!dryRun) { 161 await paintings.updateOne( 162 { _id: existing._id }, 163 { 164 $set: { 165 code, 166 bucket: "art-aesthetic-computer", 167 type: "guest", 168 }, 169 } 170 ); 171 created++; 172 } 173 174 if (processed % 100 === 0 || dryRun) { 175 console.log(` [${processed}/${s3Paintings.length}] ${dryRun ? "Would assign" : "Assigned"} code: ${code}${s3Painting.slug}`); 176 } 177 } else { 178 skipped++; 179 } 180 } else { 181 // Create new database record 182 const code = await generateUniqueCode(codeExists); 183 184 if (!dryRun) { 185 await paintings.insertOne({ 186 code, 187 slug: s3Painting.slug, 188 // user: undefined, // Don't set user field for guest paintings 189 when: s3Painting.lastModified, 190 nuked: false, 191 bucket: "art-aesthetic-computer", 192 type: "guest", 193 }); 194 created++; 195 } 196 197 if (processed % 100 === 0 || dryRun) { 198 console.log(` [${processed}/${s3Paintings.length}] ${dryRun ? "Would create" : "Created"} record with code: ${code}${s3Painting.slug}`); 199 } 200 } 201 } catch (error) { 202 console.error(` ❌ Error processing ${s3Painting.slug}:`, error.message); 203 skipped++; 204 } 205 } 206 207 console.log(`\n ✅ Processed: ${processed}`); 208 console.log(` ${dryRun ? "📝 Would create" : "💾 Created"}: ${created}`); 209 if (skipped > 0) console.log(` ⚠️ Skipped: ${skipped}`); 210 console.log(); 211 212 return { processed, created, skipped }; 213} 214 215async function run() { 216 const database = await connect(); 217 218 try { 219 const startTime = Date.now(); 220 221 // Migrate user paintings 222 const userResults = await migrateUserPaintings(database); 223 224 // Migrate guest paintings 225 const guestResults = await migrateGuestPaintings(database); 226 227 const endTime = Date.now(); 228 const duration = ((endTime - startTime) / 1000).toFixed(2); 229 230 console.log("📊 Migration Summary"); 231 console.log("==================="); 232 console.log(` User paintings: ${userResults.updated} ${dryRun ? "would be updated" : "updated"}`); 233 console.log(` Guest paintings: ${guestResults.created} ${dryRun ? "would be created" : "created"}`); 234 console.log(` Total processed: ${userResults.processed + guestResults.processed}`); 235 console.log(` Duration: ${duration}s`); 236 237 if (dryRun) { 238 console.log(`\n💡 Run without --dry-run to apply changes`); 239 } else { 240 console.log(`\n✅ Migration complete!`); 241 } 242 } finally { 243 await database.disconnect(); 244 } 245} 246 247run().catch((error) => { 248 console.error("❌ Migration failed:", error); 249 process.exit(1); 250});