dubs-server 1.0.0
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.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Regenerate EPL Matchup Images Script
|
|
4
|
+
*
|
|
5
|
+
* Regenerates ALL EPL matchup images in S3 with the latest team name overrides.
|
|
6
|
+
* Forces overwrite of existing images and updates database URLs with cache-busting.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/regenerate-epl-images.js # Process all EPL games
|
|
10
|
+
* node scripts/regenerate-epl-images.js --dry-run # Preview without making changes
|
|
11
|
+
* node scripts/regenerate-epl-images.js --limit 10 # Process only 10 games
|
|
12
|
+
* node scripts/regenerate-epl-images.js --game-id sport-xxx # Process specific game
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
require('dotenv').config();
|
|
16
|
+
const { Pool } = require('pg');
|
|
17
|
+
|
|
18
|
+
// Lazy load services
|
|
19
|
+
let matchupImageService = null;
|
|
20
|
+
let s3Service = null;
|
|
21
|
+
|
|
22
|
+
function getMatchupImageService() {
|
|
23
|
+
if (!matchupImageService) {
|
|
24
|
+
try {
|
|
25
|
+
matchupImageService = require('../services/matchupImageService');
|
|
26
|
+
console.log('β
Matchup image service loaded');
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error('β Failed to load matchup image service:', err.message);
|
|
29
|
+
console.error(' Install canvas: npm install canvas');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return matchupImageService;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getS3Service() {
|
|
37
|
+
if (!s3Service) {
|
|
38
|
+
try {
|
|
39
|
+
const S3Service = require('../services/s3Service');
|
|
40
|
+
s3Service = new S3Service();
|
|
41
|
+
if (!s3Service.isConfigured()) {
|
|
42
|
+
console.error('β S3 credentials not configured');
|
|
43
|
+
console.error(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log('β
S3 service loaded');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('β Failed to load S3 service:', err.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return s3Service;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Database connection
|
|
56
|
+
const pool = new Pool({
|
|
57
|
+
connectionString: process.env.DATABASE_URL,
|
|
58
|
+
ssl: process.env.DATABASE_URL?.includes('amazonaws') || process.env.DATABASE_URL?.includes('heroku')
|
|
59
|
+
? { rejectUnauthorized: false }
|
|
60
|
+
: false
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Track which team combos we've already processed (to avoid regenerating same image multiple times)
|
|
64
|
+
const processedCombos = new Set();
|
|
65
|
+
|
|
66
|
+
async function processGame(game, dryRun = false) {
|
|
67
|
+
const service = getMatchupImageService();
|
|
68
|
+
const s3 = getS3Service();
|
|
69
|
+
|
|
70
|
+
const sportsEvent = game.sports_event;
|
|
71
|
+
const homeTeam = sportsEvent?.strHomeTeam;
|
|
72
|
+
const awayTeam = sportsEvent?.strAwayTeam;
|
|
73
|
+
const league = 'EPL'; // Force EPL
|
|
74
|
+
|
|
75
|
+
if (!homeTeam || !awayTeam) {
|
|
76
|
+
console.log(` β οΈ Skipping ${game.game_id} - missing team names`);
|
|
77
|
+
return { skipped: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create a combo key to avoid regenerating the same image
|
|
81
|
+
const comboKey = s3.getMatchupImageKey(awayTeam, homeTeam, league);
|
|
82
|
+
const alreadyProcessed = processedCombos.has(comboKey);
|
|
83
|
+
|
|
84
|
+
console.log(` π ${homeTeam} vs ${awayTeam}`);
|
|
85
|
+
|
|
86
|
+
if (dryRun) {
|
|
87
|
+
console.log(` π DRY RUN - Would regenerate image for ${comboKey}`);
|
|
88
|
+
return { success: true, dryRun: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
let publicUrl;
|
|
93
|
+
|
|
94
|
+
if (alreadyProcessed) {
|
|
95
|
+
// Image already regenerated in this run, just get the URL
|
|
96
|
+
const cacheBuster = Date.now();
|
|
97
|
+
publicUrl = `https://${s3.bucketName}.s3.${s3.region}.amazonaws.com/${comboKey}?v=${cacheBuster}`;
|
|
98
|
+
console.log(` β»οΈ Reusing already regenerated image`);
|
|
99
|
+
} else {
|
|
100
|
+
// Generate new image
|
|
101
|
+
const result = await service.generateMatchupImage({
|
|
102
|
+
homeTeam,
|
|
103
|
+
awayTeam,
|
|
104
|
+
league,
|
|
105
|
+
width: 600,
|
|
106
|
+
height: 315
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Upload to S3 with forceOverwrite = true
|
|
110
|
+
const uploadResult = await s3.uploadMatchupImage(awayTeam, homeTeam, league, result.buffer, true);
|
|
111
|
+
publicUrl = uploadResult.publicUrl;
|
|
112
|
+
|
|
113
|
+
// Mark this combo as processed
|
|
114
|
+
processedCombos.add(comboKey);
|
|
115
|
+
|
|
116
|
+
console.log(` π Regenerated: ${comboKey}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update database with new URL (includes cache-buster)
|
|
120
|
+
await pool.query(`
|
|
121
|
+
UPDATE games
|
|
122
|
+
SET matchup_image_url = $1, updated_at = NOW()
|
|
123
|
+
WHERE game_id = $2
|
|
124
|
+
`, [publicUrl, game.game_id]);
|
|
125
|
+
|
|
126
|
+
console.log(` β
Updated: ${game.game_id}`);
|
|
127
|
+
return { success: true, url: publicUrl, regenerated: !alreadyProcessed };
|
|
128
|
+
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(` β Failed: ${err.message}`);
|
|
131
|
+
return { error: err.message };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function main() {
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log('π¨ EPL Matchup Image Regeneration Script');
|
|
138
|
+
console.log('=========================================');
|
|
139
|
+
console.log('');
|
|
140
|
+
|
|
141
|
+
// Parse arguments
|
|
142
|
+
const args = process.argv.slice(2);
|
|
143
|
+
let limit = null;
|
|
144
|
+
let gameId = null;
|
|
145
|
+
let dryRun = false;
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < args.length; i++) {
|
|
148
|
+
if (args[i] === '--limit' && args[i + 1]) {
|
|
149
|
+
limit = parseInt(args[i + 1], 10);
|
|
150
|
+
}
|
|
151
|
+
if (args[i] === '--game-id' && args[i + 1]) {
|
|
152
|
+
gameId = args[i + 1];
|
|
153
|
+
}
|
|
154
|
+
if (args[i] === '--dry-run') {
|
|
155
|
+
dryRun = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (dryRun) {
|
|
160
|
+
console.log('π DRY RUN MODE - No changes will be made');
|
|
161
|
+
console.log('');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Initialize services
|
|
165
|
+
getMatchupImageService();
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
getS3Service();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Query for EPL games
|
|
172
|
+
let query;
|
|
173
|
+
let params;
|
|
174
|
+
|
|
175
|
+
if (gameId) {
|
|
176
|
+
query = `
|
|
177
|
+
SELECT game_id, sports_event
|
|
178
|
+
FROM games
|
|
179
|
+
WHERE game_id = $1 AND game_mode = 4
|
|
180
|
+
`;
|
|
181
|
+
params = [gameId];
|
|
182
|
+
} else {
|
|
183
|
+
// Get ALL EPL games (including those with existing matchup images)
|
|
184
|
+
query = `
|
|
185
|
+
SELECT game_id, sports_event
|
|
186
|
+
FROM games
|
|
187
|
+
WHERE game_mode = 4
|
|
188
|
+
AND sports_event IS NOT NULL
|
|
189
|
+
AND (
|
|
190
|
+
sports_event->>'strLeague' ILIKE '%premier%'
|
|
191
|
+
OR sports_event->>'strLeague' = 'EPL'
|
|
192
|
+
)
|
|
193
|
+
ORDER BY created_at DESC
|
|
194
|
+
${limit ? `LIMIT ${limit}` : ''}
|
|
195
|
+
`;
|
|
196
|
+
params = [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const result = await pool.query(query, params);
|
|
200
|
+
const games = result.rows;
|
|
201
|
+
|
|
202
|
+
console.log(`π Found ${games.length} EPL games to process`);
|
|
203
|
+
console.log('');
|
|
204
|
+
|
|
205
|
+
if (games.length === 0) {
|
|
206
|
+
console.log('β
No EPL games found!');
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Collect unique team combinations
|
|
211
|
+
const uniqueCombos = new Set();
|
|
212
|
+
for (const game of games) {
|
|
213
|
+
const s3 = dryRun ? null : getS3Service();
|
|
214
|
+
const sportsEvent = game.sports_event;
|
|
215
|
+
if (sportsEvent?.strHomeTeam && sportsEvent?.strAwayTeam) {
|
|
216
|
+
// Use a simple key for counting
|
|
217
|
+
const key = `${sportsEvent.strAwayTeam}_${sportsEvent.strHomeTeam}`;
|
|
218
|
+
uniqueCombos.add(key);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
console.log(`π’ Unique team combinations: ${uniqueCombos.size}`);
|
|
222
|
+
console.log('');
|
|
223
|
+
|
|
224
|
+
// Process games
|
|
225
|
+
let processed = 0;
|
|
226
|
+
let succeeded = 0;
|
|
227
|
+
let failed = 0;
|
|
228
|
+
let skipped = 0;
|
|
229
|
+
let regenerated = 0;
|
|
230
|
+
|
|
231
|
+
for (const game of games) {
|
|
232
|
+
console.log(`[${processed + 1}/${games.length}] ${game.game_id}`);
|
|
233
|
+
|
|
234
|
+
const result = await processGame(game, dryRun);
|
|
235
|
+
processed++;
|
|
236
|
+
|
|
237
|
+
if (result.success) {
|
|
238
|
+
succeeded++;
|
|
239
|
+
if (result.regenerated) regenerated++;
|
|
240
|
+
}
|
|
241
|
+
else if (result.skipped) skipped++;
|
|
242
|
+
else failed++;
|
|
243
|
+
|
|
244
|
+
// Small delay to avoid rate limiting
|
|
245
|
+
if (!dryRun) {
|
|
246
|
+
await new Promise(r => setTimeout(r, 100));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log('');
|
|
251
|
+
console.log('π Summary');
|
|
252
|
+
console.log('==========');
|
|
253
|
+
console.log(` Total processed: ${processed}`);
|
|
254
|
+
console.log(` β
Succeeded: ${succeeded}`);
|
|
255
|
+
console.log(` π Regenerated: ${regenerated} unique images`);
|
|
256
|
+
console.log(` β οΈ Skipped: ${skipped}`);
|
|
257
|
+
console.log(` β Failed: ${failed}`);
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
if (dryRun) {
|
|
261
|
+
console.log('π DRY RUN complete - no changes were made');
|
|
262
|
+
console.log(' Run without --dry-run to apply changes');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
266
|
+
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error('β Fatal error:', err);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
} finally {
|
|
271
|
+
await pool.end();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
main().catch(err => {
|
|
276
|
+
console.error('β Unhandled error:', err);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
});
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Resize S3 Matchup Images Script
|
|
4
|
+
*
|
|
5
|
+
* Converts existing PNG matchup images on S3 to JPEG for better compression.
|
|
6
|
+
* Resizes images larger than 600x315 down to 600x315 (maintains full quality).
|
|
7
|
+
* Images already at or below 600x315 are just converted to JPEG without resizing.
|
|
8
|
+
* Processes both us-east-1 (dev) and us-east-2 (prod) buckets.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/resize-s3-matchup-images.js # Dry run (preview only)
|
|
12
|
+
* node scripts/resize-s3-matchup-images.js --execute # Actually convert and upload
|
|
13
|
+
* node scripts/resize-s3-matchup-images.js --limit 5 # Process only 5 images per bucket
|
|
14
|
+
* node scripts/resize-s3-matchup-images.js --execute --limit 10 # Convert 10 images per bucket
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
require('dotenv').config();
|
|
18
|
+
const { S3Client, ListObjectsV2Command, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3');
|
|
19
|
+
const sharp = require('sharp');
|
|
20
|
+
|
|
21
|
+
// Configuration - processes both buckets
|
|
22
|
+
const BUCKETS = [
|
|
23
|
+
{ name: 'dubs-avatars-dev', region: 'us-east-1' },
|
|
24
|
+
{ name: 'dubs-avatars-prod', region: 'us-east-2' },
|
|
25
|
+
];
|
|
26
|
+
const MATCHUPS_PREFIX = 'matchups/';
|
|
27
|
+
|
|
28
|
+
// Target size for optimization (images larger than this will be resized down)
|
|
29
|
+
const TARGET_WIDTH = 600;
|
|
30
|
+
const TARGET_HEIGHT = 315;
|
|
31
|
+
|
|
32
|
+
// Parse command line arguments
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const EXECUTE_MODE = args.includes('--execute');
|
|
35
|
+
const limitIndex = args.indexOf('--limit');
|
|
36
|
+
const LIMIT = limitIndex !== -1 ? parseInt(args[limitIndex + 1], 10) : null;
|
|
37
|
+
|
|
38
|
+
// Initialize S3 client
|
|
39
|
+
function getS3Client(region) {
|
|
40
|
+
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
|
|
41
|
+
console.error('β AWS credentials not set');
|
|
42
|
+
console.error(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return new S3Client({
|
|
47
|
+
region: region,
|
|
48
|
+
credentials: {
|
|
49
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
50
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* List all matchup images in the bucket
|
|
57
|
+
*/
|
|
58
|
+
async function listMatchupImages(s3Client, bucketName) {
|
|
59
|
+
const images = [];
|
|
60
|
+
let continuationToken = null;
|
|
61
|
+
|
|
62
|
+
console.log(`π Listing images in s3://${bucketName}/${MATCHUPS_PREFIX}...`);
|
|
63
|
+
|
|
64
|
+
do {
|
|
65
|
+
const command = new ListObjectsV2Command({
|
|
66
|
+
Bucket: bucketName,
|
|
67
|
+
Prefix: MATCHUPS_PREFIX,
|
|
68
|
+
ContinuationToken: continuationToken,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const response = await s3Client.send(command);
|
|
72
|
+
|
|
73
|
+
if (response.Contents) {
|
|
74
|
+
for (const obj of response.Contents) {
|
|
75
|
+
// Only process PNG files
|
|
76
|
+
if (obj.Key.endsWith('.png')) {
|
|
77
|
+
images.push({
|
|
78
|
+
key: obj.Key,
|
|
79
|
+
size: obj.Size,
|
|
80
|
+
lastModified: obj.LastModified,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
continuationToken = response.NextContinuationToken;
|
|
87
|
+
} while (continuationToken);
|
|
88
|
+
|
|
89
|
+
return images;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Download image from S3
|
|
94
|
+
*/
|
|
95
|
+
async function downloadImage(s3Client, bucketName, key) {
|
|
96
|
+
const command = new GetObjectCommand({
|
|
97
|
+
Bucket: bucketName,
|
|
98
|
+
Key: key,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const response = await s3Client.send(command);
|
|
102
|
+
|
|
103
|
+
// Convert stream to buffer
|
|
104
|
+
const chunks = [];
|
|
105
|
+
for await (const chunk of response.Body) {
|
|
106
|
+
chunks.push(chunk);
|
|
107
|
+
}
|
|
108
|
+
return Buffer.concat(chunks);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert image to JPEG and resize if larger than target size
|
|
113
|
+
* If image is already at or below 600x315, just converts to JPEG without resizing
|
|
114
|
+
*/
|
|
115
|
+
async function resizeImage(imageBuffer) {
|
|
116
|
+
// Get original dimensions
|
|
117
|
+
const metadata = await sharp(imageBuffer).metadata();
|
|
118
|
+
const originalWidth = metadata.width;
|
|
119
|
+
const originalHeight = metadata.height;
|
|
120
|
+
|
|
121
|
+
// Check if image is already at or below target size (no resize needed)
|
|
122
|
+
const isAlreadyOptimal = originalWidth <= TARGET_WIDTH && originalHeight <= TARGET_HEIGHT;
|
|
123
|
+
|
|
124
|
+
let newWidth, newHeight;
|
|
125
|
+
if (isAlreadyOptimal) {
|
|
126
|
+
// Already optimal size, just convert to JPEG without resizing
|
|
127
|
+
newWidth = originalWidth;
|
|
128
|
+
newHeight = originalHeight;
|
|
129
|
+
} else {
|
|
130
|
+
// Image is larger than target, resize down to target size
|
|
131
|
+
newWidth = TARGET_WIDTH;
|
|
132
|
+
newHeight = TARGET_HEIGHT;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Convert to JPEG (resize only if needed)
|
|
136
|
+
let processedBuffer = sharp(imageBuffer);
|
|
137
|
+
|
|
138
|
+
if (!isAlreadyOptimal) {
|
|
139
|
+
processedBuffer = processedBuffer.resize(newWidth, newHeight, {
|
|
140
|
+
fit: 'inside',
|
|
141
|
+
withoutEnlargement: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const resizedBuffer = await processedBuffer
|
|
146
|
+
.jpeg({
|
|
147
|
+
quality: 85, // Good balance between quality and file size
|
|
148
|
+
mozjpeg: true, // Use mozjpeg encoder for better compression
|
|
149
|
+
})
|
|
150
|
+
.toBuffer();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
buffer: resizedBuffer,
|
|
154
|
+
originalWidth,
|
|
155
|
+
originalHeight,
|
|
156
|
+
newWidth,
|
|
157
|
+
newHeight,
|
|
158
|
+
wasAlreadyResized: isAlreadyOptimal,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Upload resized image back to S3
|
|
164
|
+
* Converts PNG keys to JPEG keys
|
|
165
|
+
*/
|
|
166
|
+
async function uploadImage(s3Client, bucketName, key, imageBuffer) {
|
|
167
|
+
// Convert .png extension to .jpg
|
|
168
|
+
const newKey = key.replace(/\.png$/i, '.jpg');
|
|
169
|
+
|
|
170
|
+
const command = new PutObjectCommand({
|
|
171
|
+
Bucket: bucketName,
|
|
172
|
+
Key: newKey,
|
|
173
|
+
Body: imageBuffer,
|
|
174
|
+
ContentType: 'image/jpeg',
|
|
175
|
+
CacheControl: 'public, max-age=31536000',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await s3Client.send(command);
|
|
179
|
+
|
|
180
|
+
return newKey;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Format bytes to human readable
|
|
185
|
+
*/
|
|
186
|
+
function formatBytes(bytes) {
|
|
187
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
188
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
189
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Process images for a single bucket
|
|
194
|
+
*/
|
|
195
|
+
async function processBucket(bucketName, region) {
|
|
196
|
+
console.log('');
|
|
197
|
+
console.log('πΌοΈ S3 Matchup Image Resizer');
|
|
198
|
+
console.log('============================');
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(`π Target: s3://${bucketName}/${MATCHUPS_PREFIX}`);
|
|
201
|
+
console.log(`π Region: ${region}`);
|
|
202
|
+
console.log(`π Target size: ${TARGET_WIDTH}x${TARGET_HEIGHT} (full quality)`);
|
|
203
|
+
console.log(`π Format: PNG β JPEG (better compression)`);
|
|
204
|
+
console.log(`π Mode: ${EXECUTE_MODE ? 'π₯ EXECUTE (will modify images!)' : 'π DRY RUN (preview only)'}`);
|
|
205
|
+
if (LIMIT) console.log(`π Limit: ${LIMIT} images`);
|
|
206
|
+
console.log('');
|
|
207
|
+
|
|
208
|
+
const s3Client = getS3Client(region);
|
|
209
|
+
|
|
210
|
+
// List all matchup images
|
|
211
|
+
let images = await listMatchupImages(s3Client, bucketName);
|
|
212
|
+
console.log(`π Found ${images.length} matchup images`);
|
|
213
|
+
|
|
214
|
+
if (images.length === 0) {
|
|
215
|
+
console.log('β
No images to process!');
|
|
216
|
+
return {
|
|
217
|
+
processed: 0,
|
|
218
|
+
succeeded: 0,
|
|
219
|
+
failed: 0,
|
|
220
|
+
totalOriginalSize: 0,
|
|
221
|
+
totalNewSize: 0,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Apply limit if specified
|
|
226
|
+
if (LIMIT && images.length > LIMIT) {
|
|
227
|
+
console.log(`π Limiting to ${LIMIT} images`);
|
|
228
|
+
images = images.slice(0, LIMIT);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log('');
|
|
232
|
+
|
|
233
|
+
// Process each image
|
|
234
|
+
let processed = 0;
|
|
235
|
+
let succeeded = 0;
|
|
236
|
+
let failed = 0;
|
|
237
|
+
let totalOriginalSize = 0;
|
|
238
|
+
let totalNewSize = 0;
|
|
239
|
+
|
|
240
|
+
for (const image of images) {
|
|
241
|
+
processed++;
|
|
242
|
+
const progress = `[${processed}/${images.length}]`;
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
console.log(`${progress} Processing: ${image.key}`);
|
|
246
|
+
console.log(` Original size: ${formatBytes(image.size)}`);
|
|
247
|
+
|
|
248
|
+
// Download the image
|
|
249
|
+
const originalBuffer = await downloadImage(s3Client, bucketName, image.key);
|
|
250
|
+
totalOriginalSize += originalBuffer.length;
|
|
251
|
+
|
|
252
|
+
// Resize the image
|
|
253
|
+
const result = await resizeImage(originalBuffer);
|
|
254
|
+
totalNewSize += result.buffer.length;
|
|
255
|
+
|
|
256
|
+
const sizeSaved = originalBuffer.length - result.buffer.length;
|
|
257
|
+
const percentSaved = ((sizeSaved / originalBuffer.length) * 100).toFixed(1);
|
|
258
|
+
|
|
259
|
+
if (result.wasAlreadyResized) {
|
|
260
|
+
console.log(` Dimensions: ${result.originalWidth}x${result.originalHeight} (already optimal size, converting to JPEG)`);
|
|
261
|
+
} else {
|
|
262
|
+
console.log(` Dimensions: ${result.originalWidth}x${result.originalHeight} β ${result.newWidth}x${result.newHeight}`);
|
|
263
|
+
}
|
|
264
|
+
console.log(` Format: PNG β JPEG`);
|
|
265
|
+
console.log(` New size: ${formatBytes(result.buffer.length)} (saved ${formatBytes(sizeSaved)}, ${percentSaved}%)`);
|
|
266
|
+
|
|
267
|
+
if (EXECUTE_MODE) {
|
|
268
|
+
// Upload the resized and converted image
|
|
269
|
+
const newKey = await uploadImage(s3Client, bucketName, image.key, result.buffer);
|
|
270
|
+
console.log(` β
Uploaded as: ${newKey}`);
|
|
271
|
+
} else {
|
|
272
|
+
console.log(` βΈοΈ Skipped (dry run)`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
succeeded++;
|
|
276
|
+
} catch (err) {
|
|
277
|
+
console.error(` β Failed: ${err.message}`);
|
|
278
|
+
failed++;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log('');
|
|
282
|
+
|
|
283
|
+
// Small delay to avoid rate limiting
|
|
284
|
+
await new Promise(r => setTimeout(r, 50));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Summary for this bucket
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(`π Summary for ${bucketName}`);
|
|
290
|
+
console.log('==========');
|
|
291
|
+
console.log(` Total processed: ${processed}`);
|
|
292
|
+
console.log(` β
Succeeded: ${succeeded}`);
|
|
293
|
+
console.log(` β Failed: ${failed}`);
|
|
294
|
+
console.log('');
|
|
295
|
+
console.log(` π¦ Original total size: ${formatBytes(totalOriginalSize)}`);
|
|
296
|
+
console.log(` π¦ New total size: ${formatBytes(totalNewSize)}`);
|
|
297
|
+
if (totalOriginalSize > 0) {
|
|
298
|
+
console.log(` πΎ Total saved: ${formatBytes(totalOriginalSize - totalNewSize)} (${((1 - totalNewSize / totalOriginalSize) * 100).toFixed(1)}%)`);
|
|
299
|
+
}
|
|
300
|
+
console.log('');
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
processed,
|
|
304
|
+
succeeded,
|
|
305
|
+
failed,
|
|
306
|
+
totalOriginalSize,
|
|
307
|
+
totalNewSize,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Main function
|
|
313
|
+
*/
|
|
314
|
+
async function main() {
|
|
315
|
+
console.log('');
|
|
316
|
+
console.log('πΌοΈ S3 Matchup Image Resizer - Multi-Bucket');
|
|
317
|
+
console.log('============================================');
|
|
318
|
+
console.log('');
|
|
319
|
+
console.log(`π Processing ${BUCKETS.length} buckets:`);
|
|
320
|
+
BUCKETS.forEach(bucket => {
|
|
321
|
+
console.log(` - ${bucket.name} (${bucket.region})`);
|
|
322
|
+
});
|
|
323
|
+
console.log(`π Target size: ${TARGET_WIDTH}x${TARGET_HEIGHT} (full quality)`);
|
|
324
|
+
console.log(`π Format: PNG β JPEG (better compression)`);
|
|
325
|
+
console.log(`π Mode: ${EXECUTE_MODE ? 'π₯ EXECUTE (will modify images!)' : 'π DRY RUN (preview only)'}`);
|
|
326
|
+
if (LIMIT) console.log(`π Limit: ${LIMIT} images per bucket`);
|
|
327
|
+
console.log('');
|
|
328
|
+
|
|
329
|
+
// Process each bucket
|
|
330
|
+
const allResults = [];
|
|
331
|
+
for (const bucket of BUCKETS) {
|
|
332
|
+
const result = await processBucket(bucket.name, bucket.region);
|
|
333
|
+
allResults.push({ bucket: bucket.name, region: bucket.region, ...result });
|
|
334
|
+
|
|
335
|
+
// Add separator between buckets
|
|
336
|
+
if (bucket !== BUCKETS[BUCKETS.length - 1]) {
|
|
337
|
+
console.log('');
|
|
338
|
+
console.log('β'.repeat(60));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Overall summary
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log('π Overall Summary');
|
|
345
|
+
console.log('=================');
|
|
346
|
+
const totalProcessed = allResults.reduce((sum, r) => sum + r.processed, 0);
|
|
347
|
+
const totalSucceeded = allResults.reduce((sum, r) => sum + r.succeeded, 0);
|
|
348
|
+
const totalFailed = allResults.reduce((sum, r) => sum + r.failed, 0);
|
|
349
|
+
const totalOriginalSize = allResults.reduce((sum, r) => sum + r.totalOriginalSize, 0);
|
|
350
|
+
const totalNewSize = allResults.reduce((sum, r) => sum + r.totalNewSize, 0);
|
|
351
|
+
|
|
352
|
+
console.log(` Total processed: ${totalProcessed}`);
|
|
353
|
+
console.log(` β
Succeeded: ${totalSucceeded}`);
|
|
354
|
+
console.log(` β Failed: ${totalFailed}`);
|
|
355
|
+
console.log('');
|
|
356
|
+
console.log(` π¦ Original total size: ${formatBytes(totalOriginalSize)}`);
|
|
357
|
+
console.log(` π¦ New total size: ${formatBytes(totalNewSize)}`);
|
|
358
|
+
if (totalOriginalSize > 0) {
|
|
359
|
+
console.log(` πΎ Total saved: ${formatBytes(totalOriginalSize - totalNewSize)} (${((1 - totalNewSize / totalOriginalSize) * 100).toFixed(1)}%)`);
|
|
360
|
+
}
|
|
361
|
+
console.log('');
|
|
362
|
+
|
|
363
|
+
if (!EXECUTE_MODE) {
|
|
364
|
+
console.log('β οΈ This was a DRY RUN. To actually resize images, run:');
|
|
365
|
+
console.log(' node scripts/resize-s3-matchup-images.js --execute');
|
|
366
|
+
console.log('');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
main().catch(err => {
|
|
371
|
+
console.error('β Fatal error:', err);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
});
|
|
374
|
+
|