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,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Routes - Protected endpoints for administrative actions
|
|
3
|
+
* Only accessible to the admin wallet
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const { pool } = require('../services/db'); // Shared database pool
|
|
9
|
+
|
|
10
|
+
// Admin wallet - only this address can use admin endpoints
|
|
11
|
+
const ADMIN_WALLET = 'Hvv1ctqHLR5wonuuRguefS6EpGUe7tFRBX2YWHGr3mes';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a wallet address is an admin
|
|
15
|
+
*/
|
|
16
|
+
function isAdmin(walletAddress) {
|
|
17
|
+
return walletAddress === ADMIN_WALLET;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Middleware to verify admin wallet from request
|
|
22
|
+
* Expects wallet address in Authorization header or query param
|
|
23
|
+
*/
|
|
24
|
+
function requireAdmin(req, res, next) {
|
|
25
|
+
const walletAddress = req.headers['x-wallet-address'] || req.query.wallet;
|
|
26
|
+
|
|
27
|
+
if (!walletAddress) {
|
|
28
|
+
return res.status(401).json({
|
|
29
|
+
success: false,
|
|
30
|
+
error: 'Wallet address required'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isAdmin(walletAddress)) {
|
|
35
|
+
return res.status(403).json({
|
|
36
|
+
success: false,
|
|
37
|
+
error: 'Admin access required'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
req.adminWallet = walletAddress;
|
|
42
|
+
next();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* GET /api/admin/games/pending
|
|
47
|
+
* Get all unresolved automatic games for admin review
|
|
48
|
+
*/
|
|
49
|
+
router.get('/games/pending', requireAdmin, async (req, res) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = await pool.query(`
|
|
52
|
+
SELECT
|
|
53
|
+
g.game_id,
|
|
54
|
+
g.buy_in,
|
|
55
|
+
g.game_mode,
|
|
56
|
+
g.is_locked,
|
|
57
|
+
g.is_resolved,
|
|
58
|
+
g.automatic_status,
|
|
59
|
+
g.sports_event,
|
|
60
|
+
g.created_at,
|
|
61
|
+
g.updated_at,
|
|
62
|
+
g.matchup_image_url,
|
|
63
|
+
(SELECT COUNT(*) FROM user_game_refs ugr WHERE ugr.game_id = g.game_id AND ugr.team_choice = 'home') as home_count,
|
|
64
|
+
(SELECT COUNT(*) FROM user_game_refs ugr WHERE ugr.game_id = g.game_id AND ugr.team_choice = 'away') as away_count,
|
|
65
|
+
(SELECT COUNT(*) FROM user_game_refs ugr WHERE ugr.game_id = g.game_id AND ugr.team_choice = 'draw') as draw_count
|
|
66
|
+
FROM games g
|
|
67
|
+
WHERE g.game_mode = 4
|
|
68
|
+
AND g.is_resolved = false
|
|
69
|
+
ORDER BY g.created_at DESC
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
const games = result.rows.map(row => ({
|
|
73
|
+
gameId: row.game_id,
|
|
74
|
+
buyIn: parseFloat(row.buy_in),
|
|
75
|
+
gameMode: row.game_mode,
|
|
76
|
+
isLocked: row.is_locked,
|
|
77
|
+
isResolved: row.is_resolved,
|
|
78
|
+
automaticStatus: row.automatic_status,
|
|
79
|
+
sportsEvent: row.sports_event,
|
|
80
|
+
matchupImageUrl: row.matchup_image_url,
|
|
81
|
+
createdAt: row.created_at,
|
|
82
|
+
updatedAt: row.updated_at,
|
|
83
|
+
homeCount: parseInt(row.home_count),
|
|
84
|
+
awayCount: parseInt(row.away_count),
|
|
85
|
+
drawCount: parseInt(row.draw_count),
|
|
86
|
+
totalPlayers: parseInt(row.home_count) + parseInt(row.away_count) + parseInt(row.draw_count)
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
console.log(`[Admin] Found ${games.length} pending games`);
|
|
90
|
+
|
|
91
|
+
res.json({
|
|
92
|
+
success: true,
|
|
93
|
+
games
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('[Admin] Error fetching pending games:', error);
|
|
98
|
+
res.status(500).json({
|
|
99
|
+
success: false,
|
|
100
|
+
error: 'Failed to fetch pending games'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* POST /api/admin/games/:gameId/resolve
|
|
107
|
+
* Manually resolve a game (admin escape hatch)
|
|
108
|
+
* This updates the database only - on-chain resolution should be handled separately
|
|
109
|
+
*/
|
|
110
|
+
router.post('/games/:gameId/resolve', requireAdmin, async (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const { gameId } = req.params;
|
|
113
|
+
const { winner, homeScore, awayScore } = req.body;
|
|
114
|
+
|
|
115
|
+
console.log(`[Admin] Manual resolution requested for game: ${gameId}`);
|
|
116
|
+
console.log(`[Admin] Winner: ${winner}, Score: ${homeScore}-${awayScore}`);
|
|
117
|
+
console.log(`[Admin] Requested by: ${req.adminWallet}`);
|
|
118
|
+
|
|
119
|
+
// Validate winner
|
|
120
|
+
if (!['home', 'away', 'draw', null].includes(winner)) {
|
|
121
|
+
return res.status(400).json({
|
|
122
|
+
success: false,
|
|
123
|
+
error: 'Invalid winner. Must be "home", "away", "draw", or null (for refund)'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if game exists and is not already resolved
|
|
128
|
+
const checkResult = await pool.query(`
|
|
129
|
+
SELECT game_id, is_resolved, automatic_status, sports_event
|
|
130
|
+
FROM games WHERE game_id = $1
|
|
131
|
+
`, [gameId]);
|
|
132
|
+
|
|
133
|
+
if (checkResult.rows.length === 0) {
|
|
134
|
+
return res.status(404).json({
|
|
135
|
+
success: false,
|
|
136
|
+
error: 'Game not found'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (checkResult.rows[0].is_resolved) {
|
|
141
|
+
return res.status(400).json({
|
|
142
|
+
success: false,
|
|
143
|
+
error: 'Game is already resolved'
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Update game status in PostgreSQL
|
|
148
|
+
const result = await pool.query(`
|
|
149
|
+
UPDATE games
|
|
150
|
+
SET
|
|
151
|
+
is_resolved = true,
|
|
152
|
+
automatic_status = 'resolved',
|
|
153
|
+
sports_event = COALESCE(sports_event, '{}'::jsonb) || $1::jsonb,
|
|
154
|
+
updated_at = NOW()
|
|
155
|
+
WHERE game_id = $2
|
|
156
|
+
RETURNING *
|
|
157
|
+
`, [
|
|
158
|
+
JSON.stringify({
|
|
159
|
+
finalScore: {
|
|
160
|
+
winner,
|
|
161
|
+
homeScore: homeScore || 0,
|
|
162
|
+
awayScore: awayScore || 0,
|
|
163
|
+
resolvedAt: new Date().toISOString(),
|
|
164
|
+
resolvedBy: 'admin_manual'
|
|
165
|
+
}
|
|
166
|
+
}),
|
|
167
|
+
gameId
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
if (result.rows.length === 0) {
|
|
171
|
+
return res.status(404).json({
|
|
172
|
+
success: false,
|
|
173
|
+
error: 'Failed to update game'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(`[Admin] Successfully resolved game ${gameId} - Winner: ${winner}`);
|
|
178
|
+
|
|
179
|
+
const resolvedGame = result.rows[0];
|
|
180
|
+
const sportsEvent = resolvedGame.sports_event;
|
|
181
|
+
|
|
182
|
+
// Broadcast game state update to all clients via global chatNamespace
|
|
183
|
+
const chatNamespace = global.chatNamespace;
|
|
184
|
+
if (chatNamespace) {
|
|
185
|
+
chatNamespace.emit('game:stateUpdate', {
|
|
186
|
+
gameId,
|
|
187
|
+
isResolved: true,
|
|
188
|
+
winner,
|
|
189
|
+
homeScore: homeScore || 0,
|
|
190
|
+
awayScore: awayScore || 0,
|
|
191
|
+
resolvedBy: 'admin_manual',
|
|
192
|
+
timestamp: Date.now()
|
|
193
|
+
});
|
|
194
|
+
console.log(`[Admin] Broadcasted game:stateUpdate for ${gameId}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Send notifications to all participants (won/lost/refund)
|
|
198
|
+
try {
|
|
199
|
+
console.log(`[Admin] Sending notifications to participants...`);
|
|
200
|
+
|
|
201
|
+
// Get all participants
|
|
202
|
+
const participantsResult = await pool.query(`
|
|
203
|
+
SELECT wallet_address, team_choice
|
|
204
|
+
FROM user_game_refs
|
|
205
|
+
WHERE game_id = $1
|
|
206
|
+
`, [gameId]);
|
|
207
|
+
|
|
208
|
+
const participants = participantsResult.rows;
|
|
209
|
+
console.log(`[Admin] Found ${participants.length} participant(s) to notify`);
|
|
210
|
+
|
|
211
|
+
// Build gameInvite metadata for notifications
|
|
212
|
+
const gameInvite = {
|
|
213
|
+
gameId: gameId,
|
|
214
|
+
gameAddress: resolvedGame.game_address || '',
|
|
215
|
+
title: resolvedGame.title,
|
|
216
|
+
imageUrl: resolvedGame.image_url,
|
|
217
|
+
matchupImageUrl: resolvedGame.matchup_image_url,
|
|
218
|
+
buyIn: parseFloat(resolvedGame.buy_in),
|
|
219
|
+
homeTeam: sportsEvent?.strHomeTeam,
|
|
220
|
+
awayTeam: sportsEvent?.strAwayTeam,
|
|
221
|
+
homeTeamBadge: sportsEvent?.strHomeTeamBadge,
|
|
222
|
+
awayTeamBadge: sportsEvent?.strAwayTeamBadge,
|
|
223
|
+
league: sportsEvent?.strLeague,
|
|
224
|
+
strTimestamp: sportsEvent?.strTimestamp,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const finalScore = {
|
|
228
|
+
winner,
|
|
229
|
+
homeScore: homeScore || 0,
|
|
230
|
+
awayScore: awayScore || 0,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Send notification to each participant
|
|
234
|
+
for (const participant of participants) {
|
|
235
|
+
try {
|
|
236
|
+
const isRefund = winner === null;
|
|
237
|
+
const userWon = !isRefund && participant.team_choice === winner;
|
|
238
|
+
const notificationType = isRefund ? 'game_refund' : (userWon ? 'game_won' : 'game_lost');
|
|
239
|
+
const message = `${homeScore || 0}-${awayScore || 0}`;
|
|
240
|
+
|
|
241
|
+
// Insert notification into database
|
|
242
|
+
await pool.query(`
|
|
243
|
+
INSERT INTO chat_notifications (
|
|
244
|
+
wallet_address,
|
|
245
|
+
notification_type,
|
|
246
|
+
message,
|
|
247
|
+
game_invite,
|
|
248
|
+
final_score,
|
|
249
|
+
is_read,
|
|
250
|
+
created_at
|
|
251
|
+
) VALUES ($1, $2, $3, $4, $5, false, NOW())
|
|
252
|
+
`, [
|
|
253
|
+
participant.wallet_address,
|
|
254
|
+
notificationType,
|
|
255
|
+
message,
|
|
256
|
+
JSON.stringify(gameInvite),
|
|
257
|
+
JSON.stringify(finalScore)
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
// Emit WebSocket notification to the specific user
|
|
261
|
+
if (chatNamespace) {
|
|
262
|
+
chatNamespace.to(`user:${participant.wallet_address}`).emit('notification', {
|
|
263
|
+
type: notificationType,
|
|
264
|
+
message,
|
|
265
|
+
gameInvite,
|
|
266
|
+
finalScore,
|
|
267
|
+
timestamp: Date.now()
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
console.log(`[Admin] Notified ${participant.wallet_address}: ${notificationType}`);
|
|
272
|
+
} catch (notifyError) {
|
|
273
|
+
console.error(`[Admin] Failed to notify ${participant.wallet_address}:`, notifyError.message);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(`[Admin] Sent notifications to ${participants.length} participant(s)`);
|
|
278
|
+
} catch (notificationError) {
|
|
279
|
+
console.error('[Admin] Error sending notifications:', notificationError.message);
|
|
280
|
+
// Don't fail the request - notifications are non-critical
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
res.json({
|
|
284
|
+
success: true,
|
|
285
|
+
message: `Game ${gameId} resolved successfully`,
|
|
286
|
+
game: {
|
|
287
|
+
gameId,
|
|
288
|
+
winner,
|
|
289
|
+
homeScore: homeScore || 0,
|
|
290
|
+
awayScore: awayScore || 0,
|
|
291
|
+
resolvedAt: new Date().toISOString()
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('[Admin] Error resolving game:', error);
|
|
297
|
+
res.status(500).json({
|
|
298
|
+
success: false,
|
|
299
|
+
error: 'Failed to resolve game'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* DELETE /api/admin/games/:gameId
|
|
306
|
+
* Delete a game (admin only - for cleaning up test/duplicate games)
|
|
307
|
+
*/
|
|
308
|
+
router.delete('/games/:gameId', requireAdmin, async (req, res) => {
|
|
309
|
+
try {
|
|
310
|
+
const { gameId } = req.params;
|
|
311
|
+
|
|
312
|
+
console.log(`[Admin] Delete requested for game: ${gameId}`);
|
|
313
|
+
console.log(`[Admin] Requested by: ${req.adminWallet}`);
|
|
314
|
+
|
|
315
|
+
// First delete user_game_refs (foreign key constraint)
|
|
316
|
+
await pool.query(`DELETE FROM user_game_refs WHERE game_id = $1`, [gameId]);
|
|
317
|
+
|
|
318
|
+
// Then delete the game
|
|
319
|
+
const result = await pool.query(`
|
|
320
|
+
DELETE FROM games WHERE game_id = $1 RETURNING game_id
|
|
321
|
+
`, [gameId]);
|
|
322
|
+
|
|
323
|
+
if (result.rows.length === 0) {
|
|
324
|
+
return res.status(404).json({
|
|
325
|
+
success: false,
|
|
326
|
+
error: 'Game not found'
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(`[Admin] Successfully deleted game ${gameId}`);
|
|
331
|
+
|
|
332
|
+
res.json({
|
|
333
|
+
success: true,
|
|
334
|
+
message: `Game ${gameId} deleted successfully`
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error('[Admin] Error deleting game:', error);
|
|
339
|
+
res.status(500).json({
|
|
340
|
+
success: false,
|
|
341
|
+
error: 'Failed to delete game'
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* POST /api/admin/cache/flush-games
|
|
348
|
+
* Flush all games cache from Redis. Safe for production — requests fall through
|
|
349
|
+
* to PostgreSQL and re-warm automatically.
|
|
350
|
+
*/
|
|
351
|
+
router.post('/cache/flush-games', requireAdmin, async (req, res) => {
|
|
352
|
+
try {
|
|
353
|
+
const gamesCacheService = require('../services/gamesCacheService');
|
|
354
|
+
console.log(`[Admin] Games cache flush requested by ${req.adminWallet}`);
|
|
355
|
+
|
|
356
|
+
const result = await gamesCacheService.flushAllGamesCaches();
|
|
357
|
+
|
|
358
|
+
console.log(`[Admin] Games cache flushed: ${result.deletedKeys} keys deleted`);
|
|
359
|
+
res.json({
|
|
360
|
+
success: true,
|
|
361
|
+
message: `Flushed ${result.deletedKeys} games cache keys`,
|
|
362
|
+
deletedKeys: result.deletedKeys,
|
|
363
|
+
});
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error('[Admin] Error flushing games cache:', error);
|
|
366
|
+
res.status(500).json({ success: false, error: error.message });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
module.exports = router;
|