@pioneer-platform/pioneer-discovery 8.11.11 → 8.11.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env node
2
+
3
+ /*
4
+ * Upload Asset Icons to S3 (DigitalOcean Spaces)
5
+ *
6
+ * This script:
7
+ * 1. Reads generatedAssetData.json
8
+ * 2. For each asset, checks if local icon exists
9
+ * 3. Uploads to S3 with PUBLIC-READ ACL
10
+ * 4. Verifies upload is accessible (not 403)
11
+ * 5. Tracks progress and failures
12
+ *
13
+ * CRITICAL: Sets ACL to 'public-read' on each file to avoid 403 errors
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const { execSync } = require('child_process');
19
+ const https = require('https');
20
+
21
+ // Configuration
22
+ const S3_ENDPOINT = 'https://sfo3.digitaloceanspaces.com';
23
+ const S3_BUCKET = 'keepkey';
24
+ const S3_REGION = 'sfo3';
25
+ const CDN_URL = 'https://keepkey.sfo3.cdn.digitaloceanspaces.com';
26
+
27
+ // Paths
28
+ const DATA_FILE = path.join(__dirname, '..', 'src', 'generatedAssetData.json');
29
+ const LOCAL_COINS_DIR = path.join(__dirname, '..', '..', '..', '..', '..', 'services', 'pioneer-server', 'public', 'coins');
30
+ const PROGRESS_FILE = path.join(__dirname, '.upload-progress.json');
31
+ const FAILED_FILE = path.join(__dirname, '.upload-failed.json');
32
+
33
+ // AWS credentials from environment
34
+ const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID || 'DO00FXP8KK64LCXYAEZP';
35
+ const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY || 'Uyw/cq63rrQmFV9yy1HbovTSMNhLkEwImqPa88N/E/s';
36
+
37
+ if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
38
+ console.error('❌ Missing AWS credentials');
39
+ process.exit(1);
40
+ }
41
+
42
+ /**
43
+ * Convert assetId to base64-encoded format
44
+ */
45
+ function encodeAssetId(assetId) {
46
+ return Buffer.from(assetId).toString('base64');
47
+ }
48
+
49
+ /**
50
+ * Check if URL is accessible (returns 200)
51
+ */
52
+ async function urlExists(url, timeout = 5000) {
53
+ return new Promise((resolve) => {
54
+ const req = https.request(url, { method: 'HEAD', timeout }, (res) => {
55
+ resolve(res.statusCode === 200);
56
+ });
57
+
58
+ req.on('error', () => resolve(false));
59
+ req.on('timeout', () => {
60
+ req.destroy();
61
+ resolve(false);
62
+ });
63
+
64
+ req.end();
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Upload file to S3 with public-read ACL
70
+ */
71
+ function uploadToS3(localPath, s3Key) {
72
+ try {
73
+ const cmd = `AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} aws s3 cp "${localPath}" "s3://${S3_BUCKET}/${s3Key}" --endpoint-url=${S3_ENDPOINT} --acl public-read --region ${S3_REGION}`;
74
+
75
+ execSync(cmd, { stdio: 'pipe' });
76
+ return true;
77
+ } catch (error) {
78
+ console.error(` ❌ Upload error: ${error.message}`);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Set ACL to public-read for existing file
85
+ */
86
+ function setPublicAcl(s3Key) {
87
+ try {
88
+ const cmd = `AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} aws s3api put-object-acl --bucket ${S3_BUCKET} --key "${s3Key}" --acl public-read --endpoint-url=${S3_ENDPOINT}`;
89
+
90
+ execSync(cmd, { stdio: 'pipe' });
91
+ return true;
92
+ } catch (error) {
93
+ console.error(` ❌ ACL set error: ${error.message}`);
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Wait for specified milliseconds
100
+ */
101
+ function sleep(ms) {
102
+ return new Promise(resolve => setTimeout(resolve, ms));
103
+ }
104
+
105
+ /**
106
+ * Load progress file
107
+ */
108
+ function loadProgress() {
109
+ if (fs.existsSync(PROGRESS_FILE)) {
110
+ return JSON.parse(fs.readFileSync(PROGRESS_FILE, 'utf8'));
111
+ }
112
+ return { processed: [], uploaded: 0, verified: 0, failed: 0 };
113
+ }
114
+
115
+ /**
116
+ * Save progress file
117
+ */
118
+ function saveProgress(progress) {
119
+ fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2));
120
+ }
121
+
122
+ /**
123
+ * Load or initialize failed tracking
124
+ */
125
+ function loadFailed() {
126
+ if (fs.existsSync(FAILED_FILE)) {
127
+ return JSON.parse(fs.readFileSync(FAILED_FILE, 'utf8'));
128
+ }
129
+ return { assets: [] };
130
+ }
131
+
132
+ /**
133
+ * Save failed assets
134
+ */
135
+ function saveFailed(failed) {
136
+ fs.writeFileSync(FAILED_FILE, JSON.stringify(failed, null, 2));
137
+ }
138
+
139
+ /**
140
+ * Process a single asset
141
+ */
142
+ async function processAsset(assetId, assetData, progress, failed, stats) {
143
+ const encoded = encodeAssetId(assetId);
144
+ const localPath = path.join(LOCAL_COINS_DIR, `${encoded}.png`);
145
+ const s3Key = `coins/${encoded}.png`;
146
+ const cdnUrl = `${CDN_URL}/coins/${encoded}.png`;
147
+
148
+ stats.total++;
149
+
150
+ // Skip if already processed
151
+ if (progress.processed.includes(assetId)) {
152
+ console.log(`⏭️ [${stats.total}] Skipping (already processed): ${assetData.symbol}`);
153
+ stats.skipped++;
154
+ return;
155
+ }
156
+
157
+ console.log(`\n🔄 [${stats.total}] Processing: ${assetData.symbol} (${assetId.substring(0, 50)}...)`);
158
+
159
+ // Check if local file exists
160
+ if (!fs.existsSync(localPath)) {
161
+ console.log(` ⚠️ No local file - skipping upload`);
162
+ progress.processed.push(assetId);
163
+ stats.noLocal++;
164
+ return;
165
+ }
166
+
167
+ const fileSize = fs.statSync(localPath).size;
168
+ console.log(` 📦 Local file size: ${(fileSize / 1024).toFixed(2)} KB`);
169
+
170
+ // Upload to S3 with public-read ACL
171
+ console.log(` 📤 Uploading to S3...`);
172
+ const uploaded = uploadToS3(localPath, s3Key);
173
+
174
+ if (!uploaded) {
175
+ console.log(` ❌ Upload failed`);
176
+ failed.assets.push({
177
+ assetId,
178
+ symbol: assetData.symbol,
179
+ reason: 'upload_failed',
180
+ localPath,
181
+ s3Key,
182
+ });
183
+ progress.processed.push(assetId);
184
+ progress.failed++;
185
+ stats.uploadFailed++;
186
+ return;
187
+ }
188
+
189
+ console.log(` ✅ Uploaded successfully`);
190
+ progress.uploaded++;
191
+ stats.uploaded++;
192
+
193
+ // Wait a bit for S3 to propagate
194
+ await sleep(500);
195
+
196
+ // Verify accessibility via CDN
197
+ console.log(` 🔍 Verifying CDN access: ${cdnUrl.substring(0, 80)}...`);
198
+ const accessible = await urlExists(cdnUrl);
199
+
200
+ if (!accessible) {
201
+ console.log(` ⚠️ Not accessible (403?) - trying to set ACL...`);
202
+
203
+ const aclSet = setPublicAcl(s3Key);
204
+ if (aclSet) {
205
+ await sleep(1000); // Wait for ACL to propagate
206
+ const retryAccessible = await urlExists(cdnUrl);
207
+
208
+ if (retryAccessible) {
209
+ console.log(` ✅ Now accessible after ACL fix!`);
210
+ progress.verified++;
211
+ stats.verified++;
212
+ } else {
213
+ console.log(` ❌ Still not accessible after ACL fix`);
214
+ failed.assets.push({
215
+ assetId,
216
+ symbol: assetData.symbol,
217
+ reason: 'not_accessible_after_acl',
218
+ cdnUrl,
219
+ s3Key,
220
+ });
221
+ progress.failed++;
222
+ stats.verifyFailed++;
223
+ }
224
+ } else {
225
+ console.log(` ❌ Failed to set ACL`);
226
+ failed.assets.push({
227
+ assetId,
228
+ symbol: assetData.symbol,
229
+ reason: 'acl_set_failed',
230
+ cdnUrl,
231
+ s3Key,
232
+ });
233
+ progress.failed++;
234
+ stats.verifyFailed++;
235
+ }
236
+ } else {
237
+ console.log(` ✅ Verified accessible!`);
238
+ progress.verified++;
239
+ stats.verified++;
240
+ }
241
+
242
+ progress.processed.push(assetId);
243
+
244
+ // Save progress every 10 assets
245
+ if (stats.total % 10 === 0) {
246
+ saveProgress(progress);
247
+ saveFailed(failed);
248
+ console.log(`\n📊 Progress saved - Total: ${stats.total}, Uploaded: ${stats.uploaded}, Verified: ${stats.verified}, Failed: ${stats.uploadFailed + stats.verifyFailed}`);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Main execution
254
+ */
255
+ async function main() {
256
+ console.log('🚀 Starting S3 upload process...\n');
257
+ console.log(`📂 Local coins directory: ${LOCAL_COINS_DIR}`);
258
+ console.log(`☁️ S3 bucket: ${S3_BUCKET}`);
259
+ console.log(`🌐 CDN URL: ${CDN_URL}\n`);
260
+
261
+ // Check local directory exists
262
+ if (!fs.existsSync(LOCAL_COINS_DIR)) {
263
+ console.error(`❌ Local coins directory not found: ${LOCAL_COINS_DIR}`);
264
+ console.log('💡 Run the download script first to get icons locally');
265
+ process.exit(1);
266
+ }
267
+
268
+ // Load data and progress
269
+ const data = JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
270
+ const entries = Object.entries(data);
271
+ const progress = loadProgress();
272
+ const failed = loadFailed();
273
+
274
+ const stats = {
275
+ total: 0,
276
+ skipped: 0,
277
+ noLocal: 0,
278
+ uploaded: 0,
279
+ verified: 0,
280
+ uploadFailed: 0,
281
+ verifyFailed: 0,
282
+ };
283
+
284
+ console.log(`📊 Total assets in database: ${entries.length}`);
285
+ console.log(`📊 Already processed: ${progress.processed.length}`);
286
+ console.log(`📊 Remaining: ${entries.length - progress.processed.length}\n`);
287
+
288
+ if (progress.processed.length > 0) {
289
+ const answer = require('readline').createInterface({
290
+ input: process.stdin,
291
+ output: process.stdout
292
+ });
293
+
294
+ const response = await new Promise(resolve => {
295
+ answer.question('Resume from previous progress? (y/n): ', resolve);
296
+ });
297
+
298
+ if (response.toLowerCase() !== 'y') {
299
+ console.log('Starting fresh...');
300
+ progress.processed = [];
301
+ progress.uploaded = 0;
302
+ progress.verified = 0;
303
+ progress.failed = 0;
304
+ failed.assets = [];
305
+ }
306
+
307
+ answer.close();
308
+ }
309
+
310
+ // Process assets sequentially (to respect rate limits)
311
+ for (const [assetId, assetData] of entries) {
312
+ await processAsset(assetId, assetData, progress, failed, stats);
313
+
314
+ // Small delay between uploads to be nice to S3
315
+ await sleep(100);
316
+ }
317
+
318
+ // Final save
319
+ saveProgress(progress);
320
+ saveFailed(failed);
321
+
322
+ // Cleanup progress file if complete
323
+ if (progress.processed.length === entries.length && failed.assets.length === 0) {
324
+ fs.unlinkSync(PROGRESS_FILE);
325
+ console.log('\n✅ All assets processed successfully - progress file cleaned up');
326
+ }
327
+
328
+ // Print summary
329
+ console.log('\n' + '='.repeat(80));
330
+ console.log('📊 FINAL SUMMARY');
331
+ console.log('='.repeat(80));
332
+ console.log(`Total assets: ${entries.length}`);
333
+ console.log(`Already processed (skipped): ${stats.skipped}`);
334
+ console.log(`No local file: ${stats.noLocal}`);
335
+ console.log(`Uploaded successfully: ${stats.uploaded}`);
336
+ console.log(`Verified accessible: ${stats.verified}`);
337
+ console.log(`Upload failed: ${stats.uploadFailed}`);
338
+ console.log(`Verification failed: ${stats.verifyFailed}`);
339
+ console.log(`Total failed: ${stats.uploadFailed + stats.verifyFailed}`);
340
+ console.log('='.repeat(80));
341
+
342
+ if (failed.assets.length > 0) {
343
+ console.log(`\n⚠️ ${failed.assets.length} assets failed - see ${FAILED_FILE}`);
344
+
345
+ // Group by failure reason
346
+ const byReason = {};
347
+ failed.assets.forEach(asset => {
348
+ byReason[asset.reason] = (byReason[asset.reason] || 0) + 1;
349
+ });
350
+
351
+ console.log('\nFailure reasons:');
352
+ Object.entries(byReason).forEach(([reason, count]) => {
353
+ console.log(` ${reason}: ${count}`);
354
+ });
355
+ }
356
+
357
+ console.log('\n✅ Upload process complete!\n');
358
+ }
359
+
360
+ main().catch(err => {
361
+ console.error('❌ Fatal error:', err);
362
+ process.exit(1);
363
+ });