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,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeper Monitoring Routes
|
|
3
|
+
*
|
|
4
|
+
* Endpoints for viewing keeper health, stuck rounds, and logs
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const KeeperStateService = require('../services/keeperStateService');
|
|
10
|
+
|
|
11
|
+
const state = new KeeperStateService();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* GET /api/keeper/health
|
|
15
|
+
* Returns overall keeper health summary
|
|
16
|
+
*/
|
|
17
|
+
router.get('/health', async (req, res) => {
|
|
18
|
+
try {
|
|
19
|
+
const summary = await state.getHealthSummary();
|
|
20
|
+
const stuckRounds = await state.getStuckRounds();
|
|
21
|
+
|
|
22
|
+
const health = {
|
|
23
|
+
healthy: stuckRounds.length === 0 && (summary?.stuck_rounds || 0) === 0,
|
|
24
|
+
summary,
|
|
25
|
+
stuckRounds,
|
|
26
|
+
timestamp: new Date().toISOString()
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
res.json(health);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
res.status(500).json({ error: error.message });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* GET /api/keeper/rounds/stuck
|
|
37
|
+
* Returns list of stuck rounds
|
|
38
|
+
*/
|
|
39
|
+
router.get('/rounds/stuck', async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const stuck = await state.getStuckRounds();
|
|
42
|
+
res.json({ stuck, count: stuck.length });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
res.status(500).json({ error: error.message });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* GET /api/keeper/rounds/:roundId
|
|
50
|
+
* Returns detailed state for a specific round
|
|
51
|
+
*/
|
|
52
|
+
router.get('/rounds/:roundId', async (req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const roundId = req.params.roundId;
|
|
55
|
+
const round = await state.getRound(roundId);
|
|
56
|
+
const actions = await state.getRoundActions(roundId, 50);
|
|
57
|
+
|
|
58
|
+
if (!round) {
|
|
59
|
+
return res.status(404).json({ error: 'Round not found in database' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
res.json({
|
|
63
|
+
round,
|
|
64
|
+
actions,
|
|
65
|
+
isStuck: state.isRoundStuck(round)
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
res.status(500).json({ error: error.message });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GET /api/keeper/rounds/:roundId/actions
|
|
74
|
+
* Returns action log for a specific round
|
|
75
|
+
*/
|
|
76
|
+
router.get('/rounds/:roundId/actions', async (req, res) => {
|
|
77
|
+
try {
|
|
78
|
+
const roundId = req.params.roundId;
|
|
79
|
+
const limit = parseInt(req.query.limit) || 100;
|
|
80
|
+
const actions = await state.getRoundActions(roundId, limit);
|
|
81
|
+
|
|
82
|
+
res.json({ roundId, actions, count: actions.length });
|
|
83
|
+
} catch (error) {
|
|
84
|
+
res.status(500).json({ error: error.message });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* GET /api/keeper/dashboard
|
|
90
|
+
* Returns comprehensive dashboard data
|
|
91
|
+
*/
|
|
92
|
+
router.get('/dashboard', async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const summary = await state.getHealthSummary();
|
|
95
|
+
const stuckRounds = await state.getStuckRounds();
|
|
96
|
+
|
|
97
|
+
// Get recent rounds (last 20)
|
|
98
|
+
const { rows: recentRounds } = await state.pool.query(`
|
|
99
|
+
SELECT round_id, status, entry_count, total_pot, winner_pubkey, win_amount,
|
|
100
|
+
created_at, locked_at, revealed_at, resolved_at, reset_at, retry_count
|
|
101
|
+
FROM keeper_rounds
|
|
102
|
+
ORDER BY round_id DESC
|
|
103
|
+
LIMIT 20
|
|
104
|
+
`);
|
|
105
|
+
|
|
106
|
+
// Get health metrics (last 24 hours)
|
|
107
|
+
const { rows: healthMetrics } = await state.pool.query(`
|
|
108
|
+
SELECT *
|
|
109
|
+
FROM keeper_health
|
|
110
|
+
WHERE timestamp > NOW() - INTERVAL '24 hours'
|
|
111
|
+
ORDER BY timestamp DESC
|
|
112
|
+
LIMIT 100
|
|
113
|
+
`);
|
|
114
|
+
|
|
115
|
+
// Calculate success rate
|
|
116
|
+
const totalRounds = summary?.resolved_rounds || 0;
|
|
117
|
+
const retriedRounds = summary?.rounds_with_retries || 0;
|
|
118
|
+
const successRate = totalRounds > 0 ? ((totalRounds - retriedRounds) / totalRounds * 100).toFixed(1) : 100;
|
|
119
|
+
|
|
120
|
+
const dashboard = {
|
|
121
|
+
summary: {
|
|
122
|
+
...summary,
|
|
123
|
+
successRate: `${successRate}%`,
|
|
124
|
+
healthy: stuckRounds.length === 0
|
|
125
|
+
},
|
|
126
|
+
stuckRounds,
|
|
127
|
+
recentRounds: recentRounds.map(r => ({
|
|
128
|
+
...r,
|
|
129
|
+
total_pot_sol: r.total_pot ? (Number(r.total_pot) / 1e9).toFixed(4) : '0',
|
|
130
|
+
win_amount_sol: r.win_amount ? (Number(r.win_amount) / 1e9).toFixed(4) : '0'
|
|
131
|
+
})),
|
|
132
|
+
healthMetrics,
|
|
133
|
+
timestamp: new Date().toISOString()
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
res.json(dashboard);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
res.status(500).json({ error: error.message });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* POST /api/keeper/cleanup
|
|
144
|
+
* Manually trigger log cleanup
|
|
145
|
+
*/
|
|
146
|
+
router.post('/cleanup', async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
await state.cleanupOldLogs();
|
|
149
|
+
res.json({ success: true, message: 'Cleanup completed' });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
res.status(500).json({ error: error.message });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
module.exports = router;
|
|
156
|
+
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keeper Webhook Routes
|
|
3
|
+
*
|
|
4
|
+
* The keeper (separate dyno) calls these endpoints to trigger WebSocket broadcasts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const { instance: history } = require('../services/jackpotHistory');
|
|
10
|
+
const notificationCacheService = require('../services/notificationCacheService');
|
|
11
|
+
|
|
12
|
+
// Store service references (will be injected)
|
|
13
|
+
let chatServiceInstance = null;
|
|
14
|
+
let playerStatsServiceInstance = null;
|
|
15
|
+
let jackpotServiceInstance = null;
|
|
16
|
+
let userProfileStatsServiceInstance = null;
|
|
17
|
+
|
|
18
|
+
// Inject dependencies
|
|
19
|
+
router.setChatService = (chatService) => {
|
|
20
|
+
chatServiceInstance = chatService;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
router.setPlayerStatsService = (playerStatsService) => {
|
|
24
|
+
playerStatsServiceInstance = playerStatsService;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
router.setJackpotService = (jackpotService) => {
|
|
28
|
+
jackpotServiceInstance = jackpotService;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
router.setUserProfileStatsService = (service) => {
|
|
32
|
+
userProfileStatsServiceInstance = service;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* POST /api/keeper-webhook/timer-update
|
|
37
|
+
* Keeper calls this every 5s with current round state
|
|
38
|
+
*/
|
|
39
|
+
router.post('/timer-update', async (req, res) => {
|
|
40
|
+
try {
|
|
41
|
+
const { roundId, timeRemaining, entryCount, totalPot, status } = req.body;
|
|
42
|
+
|
|
43
|
+
if (global.io) {
|
|
44
|
+
global.io.to(`round_${roundId}`).emit('timer_update', {
|
|
45
|
+
roundId,
|
|
46
|
+
timeRemaining,
|
|
47
|
+
entryCount,
|
|
48
|
+
totalPot,
|
|
49
|
+
status,
|
|
50
|
+
timestamp: Date.now()
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
res.json({ success: true });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
res.status(500).json({ error: error.message });
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* POST /api/keeper-webhook/round-locked
|
|
62
|
+
* Keeper calls this when round locks
|
|
63
|
+
*/
|
|
64
|
+
router.post('/round-locked', async (req, res) => {
|
|
65
|
+
try {
|
|
66
|
+
const { roundId } = req.body;
|
|
67
|
+
|
|
68
|
+
console.log(`📢 Webhook: Round ${roundId} locked`);
|
|
69
|
+
|
|
70
|
+
if (global.io) {
|
|
71
|
+
global.io.to(`round_${roundId}`).emit('round_locked', {
|
|
72
|
+
roundId,
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
});
|
|
75
|
+
console.log(`⚡ Broadcasted: round_locked`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
res.json({ success: true, broadcasted: true });
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Webhook error:', error);
|
|
81
|
+
res.status(500).json({ error: error.message });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* POST /api/keeper-webhook/winner-selected
|
|
87
|
+
* Keeper calls this when winner is selected
|
|
88
|
+
* 🔥 NOW WITH EPIC CHAT ANNOUNCEMENT! 🔥
|
|
89
|
+
*/
|
|
90
|
+
router.post('/winner-selected', async (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const { roundId, winner, winAmount, totalPot, entryCount, signature, serverSeed, serverSeedHash, oracleSeed } = req.body;
|
|
93
|
+
|
|
94
|
+
console.log('━'.repeat(80));
|
|
95
|
+
console.log(`📢 WINNER WEBHOOK CALLED!`);
|
|
96
|
+
console.log(` Round: ${roundId}`);
|
|
97
|
+
console.log(` Winner: ${winner}`);
|
|
98
|
+
console.log(` Amount (lamports): ${winAmount}`);
|
|
99
|
+
console.log(` Amount (SOL): ${winAmount / 1_000_000_000}`);
|
|
100
|
+
console.log(` Total Pot: ${totalPot}`);
|
|
101
|
+
console.log(` Entry Count: ${entryCount}`);
|
|
102
|
+
console.log(` VRF Data: ${serverSeed ? '✅' : '❌'} seed, ${serverSeedHash ? '✅' : '❌'} hash, ${oracleSeed ? '✅' : '❌'} oracle`);
|
|
103
|
+
console.log(` Chat Service: ${chatServiceInstance ? '✅ AVAILABLE' : '❌ NOT AVAILABLE'}`);
|
|
104
|
+
console.log(` Socket.IO: ${global.io ? '✅ AVAILABLE' : '❌ NOT AVAILABLE'}`);
|
|
105
|
+
console.log('━'.repeat(80));
|
|
106
|
+
|
|
107
|
+
// 💾 SAVE TO HISTORY with VRF verification data
|
|
108
|
+
try {
|
|
109
|
+
await history.addRound({
|
|
110
|
+
roundId: roundId.toString(),
|
|
111
|
+
winner,
|
|
112
|
+
winAmount: winAmount.toString(),
|
|
113
|
+
totalPot: totalPot.toString(),
|
|
114
|
+
entryCount,
|
|
115
|
+
timestamp: new Date().toISOString(),
|
|
116
|
+
signature: signature || null,
|
|
117
|
+
serverSeed: serverSeed || null,
|
|
118
|
+
serverSeedHash: serverSeedHash || null,
|
|
119
|
+
oracleSeed: oracleSeed || null,
|
|
120
|
+
});
|
|
121
|
+
console.log(`💾 Winner saved to history: ${winner.slice(0, 8)}... (VRF: ${serverSeed ? 'YES' : 'NO'})`);
|
|
122
|
+
} catch (historyError) {
|
|
123
|
+
console.error('⚠️ Failed to save winner to history:', historyError.message);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 📊 TRACK WINNER IN PLAYER STATS! 📊
|
|
127
|
+
let winnerStreak = 1;
|
|
128
|
+
if (playerStatsServiceInstance) {
|
|
129
|
+
try {
|
|
130
|
+
console.log('📊 Recording win in player stats...');
|
|
131
|
+
const result = await playerStatsServiceInstance.recordWin({
|
|
132
|
+
walletAddress: winner,
|
|
133
|
+
roundId,
|
|
134
|
+
winAmount
|
|
135
|
+
});
|
|
136
|
+
winnerStreak = result?.newStreak || 1;
|
|
137
|
+
console.log(`✅ Win recorded for ${winner.slice(0, 8)}... (streak: ${winnerStreak}🔥)`);
|
|
138
|
+
} catch (statsError) {
|
|
139
|
+
console.error('⚠️ Failed to record win in stats:', statsError.message);
|
|
140
|
+
// Don't fail the whole request if stats tracking fails
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 🏆 POST EPIC WINNER ANNOUNCEMENT TO CHAT! 🏆
|
|
145
|
+
if (chatServiceInstance) {
|
|
146
|
+
try {
|
|
147
|
+
console.log('🔥 Attempting to post winner announcement...');
|
|
148
|
+
|
|
149
|
+
const chatMessage = await chatServiceInstance.postWinnerAnnouncement({
|
|
150
|
+
winner,
|
|
151
|
+
winAmount: winAmount / 1_000_000_000, // Convert lamports to SOL
|
|
152
|
+
roundId
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
console.log('✅ Chat message created:', chatMessage);
|
|
156
|
+
|
|
157
|
+
// Broadcast the epic message to chat via /chat namespace with correct event name
|
|
158
|
+
if (global.io) {
|
|
159
|
+
const chatNamespace = global.io.of('/chat');
|
|
160
|
+
chatNamespace.emit('new_message', chatMessage);
|
|
161
|
+
console.log(`🔥 EPIC WINNER ANNOUNCEMENT broadcasted to ALL chat clients!`);
|
|
162
|
+
console.log(` Message ID: ${chatMessage.id}`);
|
|
163
|
+
console.log(` Winner: ${chatMessage.walletAddress}`);
|
|
164
|
+
console.log(` Amount: ◎${chatMessage.winAmount}`);
|
|
165
|
+
} else {
|
|
166
|
+
console.warn('⚠️ Socket.IO not available - message saved but not broadcasted');
|
|
167
|
+
}
|
|
168
|
+
} catch (chatError) {
|
|
169
|
+
console.error('❌ Failed to post winner announcement to chat:');
|
|
170
|
+
console.error(' Error:', chatError.message);
|
|
171
|
+
console.error(' Stack:', chatError.stack);
|
|
172
|
+
// Don't fail the whole request if chat fails
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
console.warn('⚠️ Chat service not available - skipping winner announcement');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Look up winner's username/avatar/id for animation display + notifications
|
|
179
|
+
let winnerUsername = winner.slice(0, 4) + '...' + winner.slice(-4);
|
|
180
|
+
let winnerAvatar = null;
|
|
181
|
+
let winnerUserId = null;
|
|
182
|
+
if (chatServiceInstance?.pool) {
|
|
183
|
+
try {
|
|
184
|
+
const userResult = await chatServiceInstance.pool.query(
|
|
185
|
+
'SELECT id, username, avatar FROM users WHERE wallet_address = $1',
|
|
186
|
+
[winner]
|
|
187
|
+
);
|
|
188
|
+
if (userResult.rows.length > 0) {
|
|
189
|
+
winnerUserId = userResult.rows[0].id;
|
|
190
|
+
winnerUsername = userResult.rows[0].username || winnerUsername;
|
|
191
|
+
winnerAvatar = userResult.rows[0].avatar || null;
|
|
192
|
+
}
|
|
193
|
+
} catch (lookupErr) {
|
|
194
|
+
console.warn('⚠️ Failed to look up winner profile:', lookupErr.message);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// 🔔 SEND WIN NOTIFICATION TO WINNER (in-app + Telegram)
|
|
199
|
+
if (winnerUserId && chatServiceInstance?.pool) {
|
|
200
|
+
try {
|
|
201
|
+
const pool = chatServiceInstance.pool;
|
|
202
|
+
const winAmountSOL = (winAmount / 1_000_000_000).toFixed(4);
|
|
203
|
+
const totalPotSOL = (totalPot / 1_000_000_000).toFixed(4);
|
|
204
|
+
const notifMessage = `You won ${winAmountSOL} SOL in Jackpot Round #${roundId}!`;
|
|
205
|
+
|
|
206
|
+
const notificationData = {
|
|
207
|
+
message: notifMessage,
|
|
208
|
+
gameInvite: {
|
|
209
|
+
gameId: `jackpot-round-${roundId}`,
|
|
210
|
+
title: 'Jackpot',
|
|
211
|
+
buyIn: 0,
|
|
212
|
+
gameType: 'jackpot',
|
|
213
|
+
},
|
|
214
|
+
jackpotWin: {
|
|
215
|
+
roundId,
|
|
216
|
+
winAmount: winAmountSOL,
|
|
217
|
+
totalPot: totalPotSOL,
|
|
218
|
+
entryCount,
|
|
219
|
+
winnerUsername,
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Insert into chat_notifications
|
|
224
|
+
const notifResult = await pool.query(
|
|
225
|
+
`INSERT INTO chat_notifications (
|
|
226
|
+
user_id, notification_type, notification_data, read, created_at
|
|
227
|
+
) VALUES ($1, $2, $3, false, NOW())
|
|
228
|
+
RETURNING id`,
|
|
229
|
+
[winnerUserId, 'game_won', JSON.stringify(notificationData)]
|
|
230
|
+
);
|
|
231
|
+
const notificationId = notifResult.rows[0].id;
|
|
232
|
+
|
|
233
|
+
const winnerNotifPayload = {
|
|
234
|
+
id: notificationId,
|
|
235
|
+
type: 'game_won',
|
|
236
|
+
senderUsername: 'Jackpot',
|
|
237
|
+
senderWallet: 'system',
|
|
238
|
+
message: notifMessage,
|
|
239
|
+
gameInvite: notificationData.gameInvite,
|
|
240
|
+
jackpotWin: notificationData.jackpotWin,
|
|
241
|
+
createdAt: new Date().toISOString(),
|
|
242
|
+
read: false,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// Update Redis notification cache
|
|
246
|
+
notificationCacheService.cacheNotification(winnerUserId, winnerNotifPayload);
|
|
247
|
+
|
|
248
|
+
// Broadcast via WebSocket
|
|
249
|
+
if (global.io) {
|
|
250
|
+
const chatNamespace = global.io.of('/chat');
|
|
251
|
+
chatNamespace.to(`user-${winnerUserId}`).emit('notification', winnerNotifPayload);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Forward to Telegram
|
|
255
|
+
try {
|
|
256
|
+
const { forwardChatNotification } = require('../services/telegramNotifications');
|
|
257
|
+
const telegramMsg = `🏆 You won the Jackpot!\n\nRound #${roundId}\nWinnings: ◎${winAmountSOL}\nTotal Pot: ◎${totalPotSOL}\n${entryCount} entries`;
|
|
258
|
+
await forwardChatNotification(pool, winnerUserId, 'game_won', 'Jackpot', telegramMsg, { gameId: `jackpot-round-${roundId}` });
|
|
259
|
+
} catch (tgErr) {
|
|
260
|
+
console.warn('⚠️ Telegram notification failed:', tgErr.message);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(`🔔 Jackpot win notification sent to ${winnerUsername} (user-${winnerUserId})`);
|
|
264
|
+
} catch (notifError) {
|
|
265
|
+
console.error('⚠️ Failed to send jackpot win notification:', notifError.message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 📢 SEND LOSS NOTIFICATIONS TO ALL NON-WINNERS (in-app + Telegram)
|
|
270
|
+
if (jackpotServiceInstance && chatServiceInstance?.pool) {
|
|
271
|
+
try {
|
|
272
|
+
const entries = await jackpotServiceInstance.getRoundEntries(roundId);
|
|
273
|
+
// Deduplicate by wallet address (same player can enter multiple times)
|
|
274
|
+
const uniqueWallets = [...new Set(entries.map(e => e.player))];
|
|
275
|
+
// Exclude the winner
|
|
276
|
+
const loserWallets = uniqueWallets.filter(w => w !== winner);
|
|
277
|
+
|
|
278
|
+
if (loserWallets.length > 0) {
|
|
279
|
+
const pool = chatServiceInstance.pool;
|
|
280
|
+
const winAmountSOL = (winAmount / 1_000_000_000).toFixed(4);
|
|
281
|
+
const totalPotSOL = (totalPot / 1_000_000_000).toFixed(4);
|
|
282
|
+
|
|
283
|
+
// Batch lookup all loser user IDs
|
|
284
|
+
const userLookup = await pool.query(
|
|
285
|
+
'SELECT id, username, wallet_address FROM users WHERE wallet_address = ANY($1)',
|
|
286
|
+
[loserWallets]
|
|
287
|
+
);
|
|
288
|
+
const userMap = {};
|
|
289
|
+
for (const row of userLookup.rows) {
|
|
290
|
+
userMap[row.wallet_address] = { id: row.id, username: row.username };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
let lossNotifCount = 0;
|
|
294
|
+
for (const loserWallet of loserWallets) {
|
|
295
|
+
const user = userMap[loserWallet];
|
|
296
|
+
if (!user) continue; // Unregistered wallet, skip
|
|
297
|
+
|
|
298
|
+
const lossNotifData = {
|
|
299
|
+
message: `Jackpot Round #${roundId} won by @${winnerUsername}`,
|
|
300
|
+
gameInvite: {
|
|
301
|
+
gameId: `jackpot-round-${roundId}`,
|
|
302
|
+
title: 'Jackpot',
|
|
303
|
+
buyIn: 0,
|
|
304
|
+
gameType: 'jackpot',
|
|
305
|
+
},
|
|
306
|
+
jackpotWin: {
|
|
307
|
+
roundId,
|
|
308
|
+
winAmount: winAmountSOL,
|
|
309
|
+
totalPot: totalPotSOL,
|
|
310
|
+
entryCount,
|
|
311
|
+
winnerUsername,
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Insert into chat_notifications
|
|
317
|
+
const notifResult = await pool.query(
|
|
318
|
+
`INSERT INTO chat_notifications (
|
|
319
|
+
user_id, notification_type, notification_data, read, created_at
|
|
320
|
+
) VALUES ($1, $2, $3, false, NOW())
|
|
321
|
+
RETURNING id`,
|
|
322
|
+
[user.id, 'game_lost', JSON.stringify(lossNotifData)]
|
|
323
|
+
);
|
|
324
|
+
const notificationId = notifResult.rows[0].id;
|
|
325
|
+
|
|
326
|
+
const loserNotifPayload = {
|
|
327
|
+
id: notificationId,
|
|
328
|
+
type: 'game_lost',
|
|
329
|
+
senderUsername: 'Jackpot',
|
|
330
|
+
senderWallet: 'system',
|
|
331
|
+
message: lossNotifData.message,
|
|
332
|
+
gameInvite: lossNotifData.gameInvite,
|
|
333
|
+
jackpotWin: lossNotifData.jackpotWin,
|
|
334
|
+
createdAt: new Date().toISOString(),
|
|
335
|
+
read: false,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Update Redis notification cache
|
|
339
|
+
notificationCacheService.cacheNotification(user.id, loserNotifPayload);
|
|
340
|
+
|
|
341
|
+
// Broadcast via WebSocket
|
|
342
|
+
if (global.io) {
|
|
343
|
+
const chatNamespace = global.io.of('/chat');
|
|
344
|
+
chatNamespace.to(`user-${user.id}`).emit('notification', loserNotifPayload);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Forward to Telegram
|
|
348
|
+
try {
|
|
349
|
+
const { forwardChatNotification } = require('../services/telegramNotifications');
|
|
350
|
+
const telegramMsg = `😔 Jackpot Round #${roundId}\n\nWon by @${winnerUsername}\nPot: ◎${totalPotSOL}\n${entryCount} entries\n\nBetter luck next time!`;
|
|
351
|
+
await forwardChatNotification(pool, user.id, 'game_lost', 'Jackpot', telegramMsg, { gameId: `jackpot-round-${roundId}` });
|
|
352
|
+
} catch (tgErr) {
|
|
353
|
+
// Telegram failures are non-critical
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
lossNotifCount++;
|
|
357
|
+
} catch (insertErr) {
|
|
358
|
+
console.warn(`⚠️ Failed to send loss notification to ${user.username}:`, insertErr.message);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
console.log(`📢 Jackpot loss notifications sent to ${lossNotifCount}/${loserWallets.length} losers`);
|
|
362
|
+
}
|
|
363
|
+
} catch (entriesErr) {
|
|
364
|
+
console.error('⚠️ Failed to send jackpot loss notifications:', entriesErr.message);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Invalidate profile stats cache for all round participants
|
|
369
|
+
if (userProfileStatsServiceInstance && jackpotServiceInstance) {
|
|
370
|
+
try {
|
|
371
|
+
const entries = await jackpotServiceInstance.getRoundEntries(roundId);
|
|
372
|
+
const wallets = [...new Set(entries.map(e => e.player))];
|
|
373
|
+
for (const wallet of wallets) {
|
|
374
|
+
userProfileStatsServiceInstance.invalidateCache(wallet);
|
|
375
|
+
}
|
|
376
|
+
console.log(`📊 Profile stats cache invalidated for ${wallets.length} participants`);
|
|
377
|
+
} catch (cacheErr) {
|
|
378
|
+
console.warn('⚠️ Failed to invalidate profile stats cache:', cacheErr.message);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Broadcast winner_selected event to all clients (globally, not just room)
|
|
383
|
+
if (global.io) {
|
|
384
|
+
const winnerEvent = {
|
|
385
|
+
roundId,
|
|
386
|
+
winner,
|
|
387
|
+
winAmount,
|
|
388
|
+
totalPot,
|
|
389
|
+
entryCount,
|
|
390
|
+
winStreak: winnerStreak,
|
|
391
|
+
winnerUsername,
|
|
392
|
+
winnerAvatar,
|
|
393
|
+
timestamp: Date.now()
|
|
394
|
+
};
|
|
395
|
+
// Emit to specific room (for players in that round)
|
|
396
|
+
global.io.to(`round_${roundId}`).emit('winner_selected', winnerEvent);
|
|
397
|
+
// Also emit globally so LastWinner widget updates for everyone
|
|
398
|
+
global.io.emit('winner_selected', winnerEvent);
|
|
399
|
+
console.log(`⚡ Broadcasted: winner_selected globally to ALL clients (streak: ${winnerStreak}🔥)`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
res.json({ success: true, broadcasted: true });
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('Webhook error:', error);
|
|
405
|
+
res.status(500).json({ error: error.message });
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* POST /api/keeper-webhook/player-entry
|
|
411
|
+
* Track player entries for PNL stats
|
|
412
|
+
*/
|
|
413
|
+
router.post('/player-entry', async (req, res) => {
|
|
414
|
+
try {
|
|
415
|
+
const { walletAddress, roundId, amount } = req.body;
|
|
416
|
+
|
|
417
|
+
console.log(`📢 Webhook: Player entry - ${walletAddress.slice(0, 8)}... wagered ${amount} lamports`);
|
|
418
|
+
|
|
419
|
+
// 📊 TRACK ENTRY IN PLAYER STATS! 📊
|
|
420
|
+
if (playerStatsServiceInstance) {
|
|
421
|
+
try {
|
|
422
|
+
await playerStatsServiceInstance.recordEntry({
|
|
423
|
+
walletAddress,
|
|
424
|
+
roundId,
|
|
425
|
+
amount
|
|
426
|
+
});
|
|
427
|
+
console.log(`✅ Entry recorded in stats`);
|
|
428
|
+
} catch (statsError) {
|
|
429
|
+
console.error('⚠️ Failed to record entry in stats:', statsError.message);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
res.json({ success: true });
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error('Webhook error:', error);
|
|
436
|
+
res.status(500).json({ error: error.message });
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* POST /api/keeper-webhook/round-opened
|
|
442
|
+
* Keeper calls this when new round opens
|
|
443
|
+
*/
|
|
444
|
+
router.post('/round-opened', async (req, res) => {
|
|
445
|
+
try {
|
|
446
|
+
const { roundId } = req.body;
|
|
447
|
+
|
|
448
|
+
console.log(`📢 Webhook: New round ${roundId} opened`);
|
|
449
|
+
|
|
450
|
+
if (global.io) {
|
|
451
|
+
global.io.emit('round_opened', {
|
|
452
|
+
roundId,
|
|
453
|
+
timestamp: Date.now()
|
|
454
|
+
});
|
|
455
|
+
console.log(`⚡ Broadcasted: round_opened globally`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
res.json({ success: true, broadcasted: true });
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error('Webhook error:', error);
|
|
461
|
+
res.status(500).json({ error: error.message });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
module.exports = router;
|
|
466
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🏀 Live Scores Routes
|
|
3
|
+
*
|
|
4
|
+
* Public endpoints for fetching live sports scores
|
|
5
|
+
* No authentication required - public ESPN data
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
const {
|
|
11
|
+
getAllLivescores,
|
|
12
|
+
getLivescoresByLeague
|
|
13
|
+
} = require('../controllers/livescoresController');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* GET /api/livescores
|
|
17
|
+
* Get live scores for all leagues (MLB, NBA, NHL, NFL)
|
|
18
|
+
* @public No authentication required
|
|
19
|
+
*/
|
|
20
|
+
router.get('/', getAllLivescores);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* GET /api/livescores/:league
|
|
24
|
+
* Get live scores for a specific league
|
|
25
|
+
* @param {string} league - League name (MLB, NBA, NHL, NFL)
|
|
26
|
+
* @public No authentication required
|
|
27
|
+
*/
|
|
28
|
+
router.get('/:league', getLivescoresByLeague);
|
|
29
|
+
|
|
30
|
+
module.exports = router;
|
|
31
|
+
|