Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env node
2// Bulk fix ACLs for recent paintings by listing from S3
3
4import { S3Client, ListObjectsV2Command, PutObjectAclCommand } from '@aws-sdk/client-s3';
5import { config } from 'dotenv';
6
7config();
8
9const DRY_RUN = process.argv.includes('--dry-run');
10const DAYS = parseInt(process.argv.find(arg => arg.startsWith('--days='))?.split('=')[1]) || 30;
11
12const s3Client = new S3Client({
13 endpoint: 'https://sfo3.digitaloceanspaces.com',
14 region: 'us-east-1',
15 credentials: {
16 accessKeyId: process.env.DO_SPACES_KEY,
17 secretAccessKey: process.env.DO_SPACES_SECRET
18 }
19});
20
21console.log('╔═══════════════════════════════════════════════════════════════╗');
22console.log('║ 🔓 Fix Painting ACLs to Public-Read ║');
23console.log('╚═══════════════════════════════════════════════════════════════╝\n');
24
25if (DRY_RUN) {
26 console.log('🔍 DRY RUN MODE - No changes will be made\n');
27}
28
29const bucket = 'user-aesthetic-computer';
30const cutoffDate = new Date();
31cutoffDate.setDate(cutoffDate.getDate() - DAYS);
32
33console.log(`📊 Scanning bucket: ${bucket}`);
34console.log(` Looking for .png files modified after: ${cutoffDate.toISOString()}\n`);
35
36let processed = 0;
37let fixed = 0;
38let skipped = 0;
39let errors = 0;
40
41async function processPrefix(prefix = '') {
42 let continuationToken;
43
44 do {
45 const listCommand = new ListObjectsV2Command({
46 Bucket: bucket,
47 Prefix: prefix,
48 ContinuationToken: continuationToken,
49 MaxKeys: 1000
50 });
51
52 const response = await s3Client.send(listCommand);
53
54 if (!response.Contents) break;
55
56 for (const object of response.Contents) {
57 if (!object.Key.endsWith('.png')) continue;
58 if (!object.Key.includes('/painting/')) continue;
59 if (object.LastModified < cutoffDate) continue;
60
61 processed++;
62 const shortKey = object.Key.length > 70 ? object.Key.substring(0, 67) + '...' : object.Key;
63 console.log(`[${processed}] ${shortKey}`);
64 console.log(` Modified: ${object.LastModified.toISOString()}`);
65
66 if (!DRY_RUN) {
67 try {
68 const aclCommand = new PutObjectAclCommand({
69 Bucket: bucket,
70 Key: object.Key,
71 ACL: 'public-read'
72 });
73 await s3Client.send(aclCommand);
74 console.log(' ✅ ACL set to public-read');
75 fixed++;
76 } catch (error) {
77 console.log(` ❌ Error: ${error.message}`);
78 errors++;
79 }
80 } else {
81 console.log(' 🔍 Would set ACL to public-read');
82 }
83 }
84
85 continuationToken = response.NextContinuationToken;
86 } while (continuationToken);
87}
88
89// Process the bucket
90await processPrefix();
91
92console.log('\n' + '═'.repeat(65));
93console.log('📊 Summary:');
94console.log(` Processed: ${processed}`);
95if (!DRY_RUN) {
96 console.log(` Fixed: ${fixed}`);
97 console.log(` Errors: ${errors}`);
98} else {
99 console.log(` Would fix: ${processed}`);
100}
101console.log('═'.repeat(65) + '\n');