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,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Backfill Matchup Images Script
|
|
4
|
+
*
|
|
5
|
+
* Generates matchup images for existing sports games that don't have one.
|
|
6
|
+
* Uploads to S3 and updates the database.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/backfill-matchup-images.js # Process all games without matchup images
|
|
10
|
+
* node scripts/backfill-matchup-images.js --limit 10 # Process only 10 games
|
|
11
|
+
* node scripts/backfill-matchup-images.js --game-id sport-xxx # Process specific game
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
require('dotenv').config();
|
|
15
|
+
const { Pool } = require('pg');
|
|
16
|
+
|
|
17
|
+
// Lazy load services (will fail if canvas not installed)
|
|
18
|
+
let matchupImageService = null;
|
|
19
|
+
let s3Service = null;
|
|
20
|
+
|
|
21
|
+
function getMatchupImageService() {
|
|
22
|
+
if (!matchupImageService) {
|
|
23
|
+
try {
|
|
24
|
+
matchupImageService = require('../services/matchupImageService');
|
|
25
|
+
console.log('ā
Matchup image service loaded');
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error('ā Failed to load matchup image service:', err.message);
|
|
28
|
+
console.error(' Install canvas: npm install canvas');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return matchupImageService;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getS3Service() {
|
|
36
|
+
if (!s3Service) {
|
|
37
|
+
try {
|
|
38
|
+
const S3Service = require('../services/s3Service');
|
|
39
|
+
s3Service = new S3Service();
|
|
40
|
+
if (!s3Service.isConfigured()) {
|
|
41
|
+
console.error('ā S3 credentials not configured');
|
|
42
|
+
console.error(' Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in .env');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
console.log('ā
S3 service loaded');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error('ā Failed to load S3 service:', err.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return s3Service;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Database connection
|
|
55
|
+
const pool = new Pool({
|
|
56
|
+
connectionString: process.env.DATABASE_URL,
|
|
57
|
+
ssl: process.env.DATABASE_URL?.includes('amazonaws') || process.env.DATABASE_URL?.includes('heroku')
|
|
58
|
+
? { rejectUnauthorized: false }
|
|
59
|
+
: false
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// League name normalization
|
|
63
|
+
const leagueMap = {
|
|
64
|
+
'NATIONAL HOCKEY LEAGUE': 'NHL',
|
|
65
|
+
'NATIONAL BASKETBALL ASSOCIATION': 'NBA',
|
|
66
|
+
'NATIONAL FOOTBALL LEAGUE': 'NFL',
|
|
67
|
+
'MAJOR LEAGUE BASEBALL': 'MLB',
|
|
68
|
+
'ENGLISH PREMIER LEAGUE': 'EPL',
|
|
69
|
+
'PREMIER LEAGUE': 'EPL',
|
|
70
|
+
'NHL': 'NHL',
|
|
71
|
+
'NBA': 'NBA',
|
|
72
|
+
'NFL': 'NFL',
|
|
73
|
+
'MLB': 'MLB',
|
|
74
|
+
'EPL': 'EPL'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function normalizeLeague(league) {
|
|
78
|
+
return leagueMap[league?.toUpperCase()] || 'NHL';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function processGame(game) {
|
|
82
|
+
const service = getMatchupImageService();
|
|
83
|
+
const s3 = getS3Service();
|
|
84
|
+
|
|
85
|
+
const sportsEvent = game.sports_event;
|
|
86
|
+
const homeTeam = sportsEvent?.strHomeTeam;
|
|
87
|
+
const awayTeam = sportsEvent?.strAwayTeam;
|
|
88
|
+
const league = normalizeLeague(sportsEvent?.strLeague);
|
|
89
|
+
|
|
90
|
+
if (!homeTeam || !awayTeam) {
|
|
91
|
+
console.log(` ā ļø Skipping ${game.game_id} - missing team names`);
|
|
92
|
+
return { skipped: true };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log(` š Processing: ${awayTeam} @ ${homeTeam} (${league})`);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Check if image already exists (team-based key allows reuse)
|
|
99
|
+
const matchupKey = s3.getMatchupImageKey(awayTeam, homeTeam, league);
|
|
100
|
+
const exists = await s3.matchupImageExists(matchupKey);
|
|
101
|
+
|
|
102
|
+
let publicUrl;
|
|
103
|
+
if (exists) {
|
|
104
|
+
// Image already exists, reuse it
|
|
105
|
+
publicUrl = `https://${s3.bucketName}.s3.${s3.region}.amazonaws.com/${matchupKey}`;
|
|
106
|
+
console.log(` ā»ļø Reusing existing image for ${awayTeam} @ ${homeTeam}`);
|
|
107
|
+
} else {
|
|
108
|
+
// Generate image at full quality (600x315), will be resized to 300x158 on upload
|
|
109
|
+
const result = await service.generateMatchupImage({
|
|
110
|
+
homeTeam,
|
|
111
|
+
awayTeam,
|
|
112
|
+
league,
|
|
113
|
+
width: 600,
|
|
114
|
+
height: 315
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Upload to S3 (will check again and upload if needed)
|
|
118
|
+
const uploadResult = await s3.uploadMatchupImage(awayTeam, homeTeam, league, result.buffer);
|
|
119
|
+
publicUrl = uploadResult.publicUrl;
|
|
120
|
+
|
|
121
|
+
if (uploadResult.wasReused) {
|
|
122
|
+
console.log(` ā»ļø Image was created by another process, reusing it`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Update database
|
|
127
|
+
await pool.query(`
|
|
128
|
+
UPDATE games
|
|
129
|
+
SET matchup_image_url = $1, updated_at = NOW()
|
|
130
|
+
WHERE game_id = $2
|
|
131
|
+
`, [publicUrl, game.game_id]);
|
|
132
|
+
|
|
133
|
+
console.log(` ā
Saved: ${publicUrl}`);
|
|
134
|
+
return { success: true, url: publicUrl };
|
|
135
|
+
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(` ā Failed: ${err.message}`);
|
|
138
|
+
return { error: err.message };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function main() {
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log('šØ Matchup Image Backfill Script');
|
|
145
|
+
console.log('================================');
|
|
146
|
+
console.log('');
|
|
147
|
+
|
|
148
|
+
// Parse arguments
|
|
149
|
+
const args = process.argv.slice(2);
|
|
150
|
+
let limit = null;
|
|
151
|
+
let gameId = null;
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < args.length; i++) {
|
|
154
|
+
if (args[i] === '--limit' && args[i + 1]) {
|
|
155
|
+
limit = parseInt(args[i + 1], 10);
|
|
156
|
+
}
|
|
157
|
+
if (args[i] === '--game-id' && args[i + 1]) {
|
|
158
|
+
gameId = args[i + 1];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Initialize services
|
|
163
|
+
getMatchupImageService();
|
|
164
|
+
getS3Service();
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Query for games needing matchup images
|
|
168
|
+
let query;
|
|
169
|
+
let params;
|
|
170
|
+
|
|
171
|
+
if (gameId) {
|
|
172
|
+
query = `
|
|
173
|
+
SELECT game_id, sports_event
|
|
174
|
+
FROM games
|
|
175
|
+
WHERE game_id = $1 AND game_mode = 4
|
|
176
|
+
`;
|
|
177
|
+
params = [gameId];
|
|
178
|
+
} else {
|
|
179
|
+
query = `
|
|
180
|
+
SELECT game_id, sports_event
|
|
181
|
+
FROM games
|
|
182
|
+
WHERE game_mode = 4
|
|
183
|
+
AND matchup_image_url IS NULL
|
|
184
|
+
AND sports_event IS NOT NULL
|
|
185
|
+
ORDER BY created_at DESC
|
|
186
|
+
${limit ? `LIMIT ${limit}` : ''}
|
|
187
|
+
`;
|
|
188
|
+
params = [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await pool.query(query, params);
|
|
192
|
+
const games = result.rows;
|
|
193
|
+
|
|
194
|
+
console.log(`š Found ${games.length} games to process`);
|
|
195
|
+
console.log('');
|
|
196
|
+
|
|
197
|
+
if (games.length === 0) {
|
|
198
|
+
console.log('ā
No games need processing!');
|
|
199
|
+
process.exit(0);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Process games
|
|
203
|
+
let processed = 0;
|
|
204
|
+
let succeeded = 0;
|
|
205
|
+
let failed = 0;
|
|
206
|
+
let skipped = 0;
|
|
207
|
+
|
|
208
|
+
for (const game of games) {
|
|
209
|
+
console.log(`[${processed + 1}/${games.length}] ${game.game_id}`);
|
|
210
|
+
|
|
211
|
+
const result = await processGame(game);
|
|
212
|
+
processed++;
|
|
213
|
+
|
|
214
|
+
if (result.success) succeeded++;
|
|
215
|
+
else if (result.skipped) skipped++;
|
|
216
|
+
else failed++;
|
|
217
|
+
|
|
218
|
+
// Small delay to avoid rate limiting
|
|
219
|
+
await new Promise(r => setTimeout(r, 100));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log('š Summary');
|
|
224
|
+
console.log('==========');
|
|
225
|
+
console.log(` Total processed: ${processed}`);
|
|
226
|
+
console.log(` ā
Succeeded: ${succeeded}`);
|
|
227
|
+
console.log(` ā ļø Skipped: ${skipped}`);
|
|
228
|
+
console.log(` ā Failed: ${failed}`);
|
|
229
|
+
console.log('');
|
|
230
|
+
|
|
231
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
232
|
+
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error('ā Fatal error:', err);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
} finally {
|
|
237
|
+
await pool.end();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
main().catch(err => {
|
|
242
|
+
console.error('ā Unhandled error:', err);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Backfill missing my_signature values by querying Solana blockchain
|
|
4
|
+
*
|
|
5
|
+
* This script:
|
|
6
|
+
* 1. Finds user_game_refs records with NULL my_signature
|
|
7
|
+
* 2. Queries Solana for transactions from that wallet
|
|
8
|
+
* 3. Finds transactions that interact with the game PDA
|
|
9
|
+
* 4. Updates the database with the recovered signature
|
|
10
|
+
*
|
|
11
|
+
* Run with: node scripts/backfill-missing-signatures.js [--dry-run]
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { Pool } = require('pg');
|
|
15
|
+
const { Connection, PublicKey } = require('@solana/web3.js');
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
|
|
18
|
+
const pool = new Pool({
|
|
19
|
+
connectionString: process.env.DATABASE_URL,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Use Alchemy or other RPC
|
|
23
|
+
const ALCHEMY_KEY = process.env.ALCHEMY_API_KEY || process.env.SOLANA_RPC_API_KEY;
|
|
24
|
+
const RPC_URL = ALCHEMY_KEY
|
|
25
|
+
? `https://solana-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`
|
|
26
|
+
: 'https://api.devnet.solana.com';
|
|
27
|
+
|
|
28
|
+
const connection = new Connection(RPC_URL, 'confirmed');
|
|
29
|
+
|
|
30
|
+
const isDryRun = process.argv.includes('--dry-run');
|
|
31
|
+
|
|
32
|
+
async function findJoinTransactionForGame(walletAddress, gameAddress) {
|
|
33
|
+
try {
|
|
34
|
+
const wallet = new PublicKey(walletAddress);
|
|
35
|
+
const gamePda = new PublicKey(gameAddress);
|
|
36
|
+
|
|
37
|
+
console.log(` š Searching for JOIN transaction...`);
|
|
38
|
+
console.log(` Wallet: ${walletAddress.slice(0, 8)}...`);
|
|
39
|
+
console.log(` Game PDA: ${gameAddress.slice(0, 8)}...`);
|
|
40
|
+
|
|
41
|
+
// Get signatures for the wallet (last 100 transactions)
|
|
42
|
+
const signatures = await connection.getSignaturesForAddress(wallet, {
|
|
43
|
+
limit: 100,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log(` Found ${signatures.length} wallet transactions to check`);
|
|
47
|
+
|
|
48
|
+
// Check each transaction to see if it involves the game PDA
|
|
49
|
+
for (const sigInfo of signatures) {
|
|
50
|
+
try {
|
|
51
|
+
const tx = await connection.getTransaction(sigInfo.signature, {
|
|
52
|
+
maxSupportedTransactionVersion: 0,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!tx || !tx.transaction) continue;
|
|
56
|
+
|
|
57
|
+
// Get the account keys from the transaction
|
|
58
|
+
const accountKeys = tx.transaction.message.staticAccountKeys ||
|
|
59
|
+
tx.transaction.message.accountKeys || [];
|
|
60
|
+
|
|
61
|
+
// Check if the game PDA is in the transaction accounts
|
|
62
|
+
const hasGamePda = accountKeys.some(key =>
|
|
63
|
+
key.toString() === gameAddress
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (hasGamePda) {
|
|
67
|
+
console.log(` ā
Found matching transaction: ${sigInfo.signature.slice(0, 16)}...`);
|
|
68
|
+
return sigInfo.signature;
|
|
69
|
+
}
|
|
70
|
+
} catch (txError) {
|
|
71
|
+
// Skip transactions we can't parse
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(` ā No matching transaction found`);
|
|
77
|
+
return null;
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(` Error searching for transaction:`, error.message);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function backfillMissingSignatures() {
|
|
86
|
+
console.log('š§ Backfill Missing Signatures Script');
|
|
87
|
+
console.log('=====================================');
|
|
88
|
+
console.log(`Mode: ${isDryRun ? 'š DRY RUN (no changes will be made)' : 'š¾ LIVE (will update database)'}`);
|
|
89
|
+
console.log(`RPC: ${RPC_URL.includes('alchemy') ? 'Alchemy' : 'Public RPC'}`);
|
|
90
|
+
console.log('');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Find records with missing signatures
|
|
94
|
+
const result = await pool.query(`
|
|
95
|
+
SELECT
|
|
96
|
+
ugr.wallet_address,
|
|
97
|
+
ugr.game_id,
|
|
98
|
+
ugr.role,
|
|
99
|
+
g.game_address,
|
|
100
|
+
g.buy_in,
|
|
101
|
+
ugr.joined_at
|
|
102
|
+
FROM user_game_refs ugr
|
|
103
|
+
LEFT JOIN games g ON ugr.game_id = g.game_id
|
|
104
|
+
WHERE (ugr.my_signature IS NULL OR ugr.my_signature = '')
|
|
105
|
+
AND g.game_address IS NOT NULL
|
|
106
|
+
ORDER BY ugr.joined_at DESC
|
|
107
|
+
`);
|
|
108
|
+
|
|
109
|
+
if (result.rows.length === 0) {
|
|
110
|
+
console.log('ā
No records with missing signatures found!');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log(`š Found ${result.rows.length} records with missing signatures:\n`);
|
|
115
|
+
|
|
116
|
+
let recovered = 0;
|
|
117
|
+
let failed = 0;
|
|
118
|
+
|
|
119
|
+
for (const row of result.rows) {
|
|
120
|
+
console.log(`\nš Processing: ${row.game_id}`);
|
|
121
|
+
console.log(` Wallet: ${row.wallet_address.slice(0, 8)}...`);
|
|
122
|
+
console.log(` Role: ${row.role}`);
|
|
123
|
+
console.log(` Joined: ${row.joined_at}`);
|
|
124
|
+
|
|
125
|
+
const signature = await findJoinTransactionForGame(
|
|
126
|
+
row.wallet_address,
|
|
127
|
+
row.game_address
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (signature) {
|
|
131
|
+
const explorerUrl = `https://explorer.solana.com/tx/${signature}?cluster=${
|
|
132
|
+
process.env.SOLANA_NETWORK || 'devnet'
|
|
133
|
+
}`;
|
|
134
|
+
|
|
135
|
+
if (isDryRun) {
|
|
136
|
+
console.log(` š DRY RUN: Would update with signature: ${signature.slice(0, 20)}...`);
|
|
137
|
+
recovered++;
|
|
138
|
+
} else {
|
|
139
|
+
// Update the database
|
|
140
|
+
await pool.query(`
|
|
141
|
+
UPDATE user_game_refs
|
|
142
|
+
SET my_signature = $1, my_explorer_url = $2
|
|
143
|
+
WHERE wallet_address = $3 AND game_id = $4
|
|
144
|
+
`, [signature, explorerUrl, row.wallet_address, row.game_id]);
|
|
145
|
+
|
|
146
|
+
console.log(` š¾ Updated database with recovered signature`);
|
|
147
|
+
recovered++;
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.log(` ā ļø Could not recover signature (transaction may be too old or not found)`);
|
|
151
|
+
failed++;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Rate limit to avoid RPC throttling (2 seconds between records)
|
|
155
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('\n=====================================');
|
|
159
|
+
console.log('š Summary:');
|
|
160
|
+
console.log(` ā
Recovered: ${recovered}`);
|
|
161
|
+
console.log(` ā Failed: ${failed}`);
|
|
162
|
+
console.log(` š Total: ${result.rows.length}`);
|
|
163
|
+
|
|
164
|
+
if (isDryRun && recovered > 0) {
|
|
165
|
+
console.log('\nš” Run without --dry-run to apply changes');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
} catch (error) {
|
|
169
|
+
console.error('ā Error:', error);
|
|
170
|
+
} finally {
|
|
171
|
+
await pool.end();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
backfillMissingSignatures();
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Backfill Missing Referral Earnings
|
|
4
|
+
*
|
|
5
|
+
* This script finds all resolved games where:
|
|
6
|
+
* - The game creator has a referrer
|
|
7
|
+
* - No referral_earnings record exists
|
|
8
|
+
*
|
|
9
|
+
* And creates the missing referral_earnings records.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/backfill-referral-earnings.js --dry-run # Preview changes
|
|
13
|
+
* node scripts/backfill-referral-earnings.js # Execute backfill
|
|
14
|
+
*
|
|
15
|
+
* On Heroku:
|
|
16
|
+
* heroku run node scripts/backfill-referral-earnings.js --app dubs-server-prod --dry-run
|
|
17
|
+
* heroku run node scripts/backfill-referral-earnings.js --app dubs-server-prod
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
require('dotenv').config();
|
|
21
|
+
const { Pool } = require('pg');
|
|
22
|
+
|
|
23
|
+
// Parse command line arguments
|
|
24
|
+
const isDryRun = process.argv.includes('--dry-run');
|
|
25
|
+
|
|
26
|
+
// Configuration
|
|
27
|
+
const REFERRAL_COMMISSION_RATE = 0.01; // 1%
|
|
28
|
+
const LAMPORTS_PER_SOL = 1_000_000_000;
|
|
29
|
+
|
|
30
|
+
// Database connection
|
|
31
|
+
const pool = new Pool({
|
|
32
|
+
connectionString: process.env.DATABASE_URL,
|
|
33
|
+
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
async function findMissingReferralEarnings() {
|
|
37
|
+
console.log('š Finding resolved games with missing referral earnings...\n');
|
|
38
|
+
|
|
39
|
+
const result = await pool.query(`
|
|
40
|
+
SELECT
|
|
41
|
+
g.game_id,
|
|
42
|
+
g.game_type,
|
|
43
|
+
g.buy_in,
|
|
44
|
+
g.title,
|
|
45
|
+
g.created_at as game_created_at,
|
|
46
|
+
g.updated_at as game_resolved_at,
|
|
47
|
+
COALESCE(array_length(g.home_team_players, 1), 0) as home_count,
|
|
48
|
+
COALESCE(array_length(g.away_team_players, 1), 0) as away_count,
|
|
49
|
+
COALESCE(array_length(g.draw_team_players, 1), 0) as draw_count,
|
|
50
|
+
g.created_by as creator_wallet,
|
|
51
|
+
creator.id as creator_user_id,
|
|
52
|
+
creator.username as creator_username,
|
|
53
|
+
referrer.id as referrer_user_id,
|
|
54
|
+
referrer.wallet_address as referrer_wallet,
|
|
55
|
+
referrer.username as referrer_username
|
|
56
|
+
FROM games g
|
|
57
|
+
JOIN users creator ON g.created_by = creator.wallet_address
|
|
58
|
+
JOIN users referrer ON creator.referral_code = referrer.my_referral_code
|
|
59
|
+
WHERE g.is_resolved = true
|
|
60
|
+
AND NOT EXISTS (
|
|
61
|
+
SELECT 1 FROM referral_earnings
|
|
62
|
+
WHERE game_id = g.game_id
|
|
63
|
+
)
|
|
64
|
+
ORDER BY g.created_at ASC
|
|
65
|
+
`);
|
|
66
|
+
|
|
67
|
+
return result.rows;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function backfillReferralEarning(game) {
|
|
71
|
+
const playerCount = game.home_count + game.away_count + game.draw_count;
|
|
72
|
+
const buyInSOL = parseFloat(game.buy_in) || 0;
|
|
73
|
+
const potSizeSOL = buyInSOL * playerCount;
|
|
74
|
+
const potSizeLamports = Math.floor(potSizeSOL * LAMPORTS_PER_SOL);
|
|
75
|
+
const commissionLamports = Math.floor(potSizeLamports * REFERRAL_COMMISSION_RATE);
|
|
76
|
+
|
|
77
|
+
if (commissionLamports <= 0) {
|
|
78
|
+
console.log(` āļø Skipping ${game.game_id} - pot too small (${potSizeSOL} SOL)`);
|
|
79
|
+
return { skipped: true, reason: 'pot_too_small' };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(` š ${game.game_id}`);
|
|
83
|
+
console.log(` Creator: ${game.creator_username} (${game.creator_wallet.slice(0, 8)}...)`);
|
|
84
|
+
console.log(` Referrer: ${game.referrer_username} (${game.referrer_wallet.slice(0, 8)}...)`);
|
|
85
|
+
console.log(` Pot: ${potSizeSOL} SOL (${playerCount} players Ć ${buyInSOL} SOL)`);
|
|
86
|
+
console.log(` Commission: ${commissionLamports / LAMPORTS_PER_SOL} SOL (1%)`);
|
|
87
|
+
|
|
88
|
+
if (isDryRun) {
|
|
89
|
+
console.log(` šø DRY RUN - would insert referral_earnings record\n`);
|
|
90
|
+
return { skipped: false, dryRun: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const insertResult = await pool.query(`
|
|
95
|
+
INSERT INTO referral_earnings (
|
|
96
|
+
referrer_user_id,
|
|
97
|
+
referrer_wallet,
|
|
98
|
+
referee_user_id,
|
|
99
|
+
referee_wallet,
|
|
100
|
+
game_id,
|
|
101
|
+
game_type,
|
|
102
|
+
pot_size,
|
|
103
|
+
referee_buy_in,
|
|
104
|
+
referee_won,
|
|
105
|
+
referee_payout,
|
|
106
|
+
commission_rate,
|
|
107
|
+
commission_amount,
|
|
108
|
+
status,
|
|
109
|
+
paid_at,
|
|
110
|
+
payout_tx_signature,
|
|
111
|
+
notes
|
|
112
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
|
113
|
+
ON CONFLICT (referee_wallet, game_id) DO NOTHING
|
|
114
|
+
RETURNING id
|
|
115
|
+
`, [
|
|
116
|
+
game.referrer_user_id,
|
|
117
|
+
game.referrer_wallet,
|
|
118
|
+
game.creator_user_id,
|
|
119
|
+
game.creator_wallet,
|
|
120
|
+
game.game_id,
|
|
121
|
+
game.game_type || 'sports',
|
|
122
|
+
potSizeLamports,
|
|
123
|
+
potSizeLamports, // Using pot size as "buy-in" since commission is based on whole pot
|
|
124
|
+
true, // Game was resolved
|
|
125
|
+
0,
|
|
126
|
+
REFERRAL_COMMISSION_RATE,
|
|
127
|
+
commissionLamports,
|
|
128
|
+
'paid', // Already paid on-chain
|
|
129
|
+
game.game_resolved_at || new Date(),
|
|
130
|
+
null, // No tx signature available for backfilled records
|
|
131
|
+
'Backfilled - on-chain payment occurred at game resolution'
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
if (insertResult.rows.length > 0) {
|
|
135
|
+
console.log(` ā
Created referral_earnings record (ID: ${insertResult.rows[0].id})\n`);
|
|
136
|
+
return { skipped: false, inserted: true, id: insertResult.rows[0].id };
|
|
137
|
+
} else {
|
|
138
|
+
console.log(` ā ļø Record already exists (conflict)\n`);
|
|
139
|
+
return { skipped: true, reason: 'conflict' };
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(` ā Error: ${error.message}\n`);
|
|
143
|
+
return { skipped: true, reason: 'error', error: error.message };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function main() {
|
|
148
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
149
|
+
console.log(' Referral Earnings Backfill Script');
|
|
150
|
+
console.log(` Mode: ${isDryRun ? 'šø DRY RUN (no changes will be made)' : 'š¹ LIVE (will insert records)'}`);
|
|
151
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const missingGames = await findMissingReferralEarnings();
|
|
155
|
+
|
|
156
|
+
if (missingGames.length === 0) {
|
|
157
|
+
console.log('ā
No missing referral earnings found. All resolved games with referrers have records.\n');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(`Found ${missingGames.length} resolved game(s) with referrers but missing referral_earnings\n`);
|
|
162
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
163
|
+
|
|
164
|
+
let inserted = 0;
|
|
165
|
+
let skipped = 0;
|
|
166
|
+
let totalCommissionLamports = 0;
|
|
167
|
+
|
|
168
|
+
for (const game of missingGames) {
|
|
169
|
+
const result = await backfillReferralEarning(game);
|
|
170
|
+
|
|
171
|
+
if (result.skipped) {
|
|
172
|
+
skipped++;
|
|
173
|
+
} else {
|
|
174
|
+
inserted++;
|
|
175
|
+
const playerCount = game.home_count + game.away_count + game.draw_count;
|
|
176
|
+
const potSizeLamports = Math.floor(parseFloat(game.buy_in) * playerCount * LAMPORTS_PER_SOL);
|
|
177
|
+
totalCommissionLamports += Math.floor(potSizeLamports * REFERRAL_COMMISSION_RATE);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
182
|
+
console.log('š Summary:');
|
|
183
|
+
console.log(` Total games processed: ${missingGames.length}`);
|
|
184
|
+
console.log(` Records ${isDryRun ? 'to be inserted' : 'inserted'}: ${inserted}`);
|
|
185
|
+
console.log(` Skipped: ${skipped}`);
|
|
186
|
+
console.log(` Total commission: ${totalCommissionLamports / LAMPORTS_PER_SOL} SOL`);
|
|
187
|
+
|
|
188
|
+
if (isDryRun) {
|
|
189
|
+
console.log('\nšø DRY RUN complete. Run without --dry-run to execute backfill.');
|
|
190
|
+
} else {
|
|
191
|
+
console.log('\nā
Backfill complete!');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('ā Fatal error:', error);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
} finally {
|
|
198
|
+
await pool.end();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
main();
|