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,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🏀 Live Scores Controller
|
|
3
|
+
*
|
|
4
|
+
* Fetches live sports scores from ESPN API
|
|
5
|
+
* Supports: MLB, NBA, NHL, NFL, EPL, UFC, NCAAF, NCAAB
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
|
|
10
|
+
const ESPN_URLS = {
|
|
11
|
+
MLB: 'https://site.api.espn.com/apis/site/v2/sports/baseball/mlb/scoreboard',
|
|
12
|
+
NBA: 'https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard',
|
|
13
|
+
NHL: 'https://site.api.espn.com/apis/site/v2/sports/hockey/nhl/scoreboard',
|
|
14
|
+
NFL: 'https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard',
|
|
15
|
+
EPL: 'https://site.api.espn.com/apis/site/v2/sports/soccer/eng.1/scoreboard',
|
|
16
|
+
UFC: 'https://site.api.espn.com/apis/site/v2/sports/mma/ufc/scoreboard',
|
|
17
|
+
NCAAF: 'https://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard',
|
|
18
|
+
NCAAB: 'https://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch and parse scores for a specific league
|
|
23
|
+
*/
|
|
24
|
+
async function fetchScoresForLeague(url, league) {
|
|
25
|
+
try {
|
|
26
|
+
console.log(`[LiveScores] Fetching scores from: ${url}`);
|
|
27
|
+
const { data } = await axios.get(url);
|
|
28
|
+
|
|
29
|
+
let scores = data.events.map(event => {
|
|
30
|
+
const competition = event.competitions?.[0];
|
|
31
|
+
const competitors = competition?.competitors || [];
|
|
32
|
+
const statusObj = event.status || {};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
date: event.date.split('T')[0],
|
|
36
|
+
game: event.name,
|
|
37
|
+
status: statusObj.type?.description || 'Unknown',
|
|
38
|
+
// Period/quarter info from ESPN
|
|
39
|
+
period: statusObj.period || null,
|
|
40
|
+
displayClock: statusObj.displayClock || null,
|
|
41
|
+
detail: statusObj.type?.detail || null,
|
|
42
|
+
shortDetail: statusObj.type?.shortDetail || null,
|
|
43
|
+
competitors: competitors.map(team => ({
|
|
44
|
+
name: team.team?.displayName,
|
|
45
|
+
homeAway: team.homeAway,
|
|
46
|
+
score: parseInt(team.score, 10) || 0,
|
|
47
|
+
logo: team.team?.logo || null,
|
|
48
|
+
abbreviation: team.team?.abbreviation || ''
|
|
49
|
+
}))
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// In development, append mock data for testing
|
|
54
|
+
if (process.env.NODE_ENV === 'development') {
|
|
55
|
+
const today = new Date().toISOString().split('T')[0];
|
|
56
|
+
|
|
57
|
+
// Mock NBA game (Amy wins)
|
|
58
|
+
if (league === 'NBA') {
|
|
59
|
+
scores.push({
|
|
60
|
+
date: today,
|
|
61
|
+
game: "Amy @ Adam",
|
|
62
|
+
status: "Final",
|
|
63
|
+
competitors: [
|
|
64
|
+
{ name: "Amy", homeAway: "away", score: 3 },
|
|
65
|
+
{ name: "Adam", homeAway: "home", score: 2 }
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Mock EPL game (Draw - equal scores for testing draw betting)
|
|
71
|
+
if (league === 'EPL') {
|
|
72
|
+
scores.push({
|
|
73
|
+
date: today,
|
|
74
|
+
game: "Chelsea @ Arsenal",
|
|
75
|
+
status: "Final",
|
|
76
|
+
competitors: [
|
|
77
|
+
{ name: "Chelsea", homeAway: "away", score: 1 },
|
|
78
|
+
{ name: "Arsenal", homeAway: "home", score: 1 }
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Mock NCAAF game (Ohio State wins)
|
|
84
|
+
if (league === 'NCAAF') {
|
|
85
|
+
scores.push({
|
|
86
|
+
date: today,
|
|
87
|
+
game: "Michigan @ Ohio State",
|
|
88
|
+
status: "Final",
|
|
89
|
+
competitors: [
|
|
90
|
+
{ name: "Michigan", homeAway: "away", score: 24 },
|
|
91
|
+
{ name: "Ohio State", homeAway: "home", score: 31 }
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Mock NCAAB game (Duke wins)
|
|
97
|
+
if (league === 'NCAAB') {
|
|
98
|
+
scores.push({
|
|
99
|
+
date: today,
|
|
100
|
+
game: "North Carolina @ Duke",
|
|
101
|
+
status: "Final",
|
|
102
|
+
competitors: [
|
|
103
|
+
{ name: "North Carolina", homeAway: "away", score: 78 },
|
|
104
|
+
{ name: "Duke", homeAway: "home", score: 85 }
|
|
105
|
+
]
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`[LiveScores] Successfully fetched ${scores.length} games`);
|
|
111
|
+
return scores;
|
|
112
|
+
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(`[LiveScores] Failed to fetch from ${url}:`, error.message);
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
121
|
+
* 🥊 UFC SCORES - ISOLATED HANDLER
|
|
122
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
123
|
+
* This function is completely separate from team sports to prevent regressions.
|
|
124
|
+
* UFC data structure is different: individual fighters with winner:boolean
|
|
125
|
+
* instead of teams with scores.
|
|
126
|
+
*
|
|
127
|
+
* Transforms UFC data to match the standard format expected by the oracle:
|
|
128
|
+
* - winner:true → score: 1
|
|
129
|
+
* - winner:false → score: 0
|
|
130
|
+
* - competitor order 1 → homeAway: 'home'
|
|
131
|
+
* - competitor order 2 → homeAway: 'away'
|
|
132
|
+
* ═══════════════════════════════════════════════════════════════════════════
|
|
133
|
+
*/
|
|
134
|
+
async function fetchUFCScores(url) {
|
|
135
|
+
try {
|
|
136
|
+
console.log(`[LiveScores:UFC] Fetching UFC scores from: ${url}`);
|
|
137
|
+
const { data } = await axios.get(url);
|
|
138
|
+
|
|
139
|
+
const scores = [];
|
|
140
|
+
|
|
141
|
+
// ESPN UFC returns events, each containing multiple competitions (fights)
|
|
142
|
+
if (!data.events || !Array.isArray(data.events)) {
|
|
143
|
+
console.log(`[LiveScores:UFC] No events found in response`);
|
|
144
|
+
return scores;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const event of data.events) {
|
|
148
|
+
// Each event (e.g., "UFC 324") contains multiple competitions (fights)
|
|
149
|
+
if (!event.competitions || !Array.isArray(event.competitions)) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const competition of event.competitions) {
|
|
154
|
+
// Each competition is a single fight between two fighters
|
|
155
|
+
const competitors = competition.competitors || [];
|
|
156
|
+
|
|
157
|
+
if (competitors.length !== 2) {
|
|
158
|
+
continue; // Skip if not exactly 2 fighters
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Determine fight status
|
|
162
|
+
const status = competition.status?.type;
|
|
163
|
+
let statusDescription = status?.description || 'Unknown';
|
|
164
|
+
|
|
165
|
+
// Map UFC status to standard format the oracle understands
|
|
166
|
+
// UFC uses: "Scheduled", "In Progress", "Final", etc.
|
|
167
|
+
if (status?.completed === true || status?.state === 'post') {
|
|
168
|
+
statusDescription = 'Final';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Extract UFC-specific live data
|
|
172
|
+
const currentRound = competition.status?.period || 0;
|
|
173
|
+
const totalRounds = competition.format?.regulation?.periods || 3; // 3 for regular, 5 for title fights
|
|
174
|
+
const clock = competition.status?.displayClock || '';
|
|
175
|
+
const fightState = status?.state || 'pre'; // 'pre', 'in', 'post'
|
|
176
|
+
const statusDetail = status?.detail || '';
|
|
177
|
+
const statusShortDetail = status?.shortDetail || '';
|
|
178
|
+
|
|
179
|
+
// Parse competitors - order 1 is "home", order 2 is "away"
|
|
180
|
+
// Transform winner:boolean to score (1 for winner, 0 for loser)
|
|
181
|
+
const fighter1 = competitors.find(c => c.order === 1) || competitors[0];
|
|
182
|
+
const fighter2 = competitors.find(c => c.order === 2) || competitors[1];
|
|
183
|
+
|
|
184
|
+
// Extract fighter records (e.g., "26-5-0")
|
|
185
|
+
const fighter1Record = fighter1.records?.find(r => r.name === 'overall')?.summary || '';
|
|
186
|
+
const fighter2Record = fighter2.records?.find(r => r.name === 'overall')?.summary || '';
|
|
187
|
+
|
|
188
|
+
const transformedCompetitors = [
|
|
189
|
+
{
|
|
190
|
+
name: fighter1.athlete?.fullName || fighter1.athlete?.displayName || 'Unknown',
|
|
191
|
+
homeAway: 'home',
|
|
192
|
+
score: fighter1.winner === true ? 1 : 0,
|
|
193
|
+
winner: fighter1.winner || false,
|
|
194
|
+
logo: fighter1.athlete?.flag?.href || null,
|
|
195
|
+
headshot: fighter1.id ? `https://a.espncdn.com/combiner/i?img=/i/headshots/mma/players/full/${fighter1.id}.png&w=350&h=254` : null,
|
|
196
|
+
country: fighter1.athlete?.flag?.alt || null,
|
|
197
|
+
abbreviation: fighter1.athlete?.shortName || '',
|
|
198
|
+
record: fighter1Record,
|
|
199
|
+
athleteId: fighter1.id || null
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: fighter2.athlete?.fullName || fighter2.athlete?.displayName || 'Unknown',
|
|
203
|
+
homeAway: 'away',
|
|
204
|
+
score: fighter2.winner === true ? 1 : 0,
|
|
205
|
+
winner: fighter2.winner || false,
|
|
206
|
+
logo: fighter2.athlete?.flag?.href || null,
|
|
207
|
+
headshot: fighter2.id ? `https://a.espncdn.com/combiner/i?img=/i/headshots/mma/players/full/${fighter2.id}.png&w=350&h=254` : null,
|
|
208
|
+
country: fighter2.athlete?.flag?.alt || null,
|
|
209
|
+
abbreviation: fighter2.athlete?.shortName || '',
|
|
210
|
+
record: fighter2Record,
|
|
211
|
+
athleteId: fighter2.id || null
|
|
212
|
+
}
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
// Extract date from competition or event
|
|
216
|
+
const fightDate = competition.date || event.date;
|
|
217
|
+
const dateOnly = fightDate ? fightDate.split('T')[0] : null;
|
|
218
|
+
|
|
219
|
+
// Build the game name as "Fighter1 vs Fighter2"
|
|
220
|
+
const gameName = `${transformedCompetitors[0].name} vs ${transformedCompetitors[1].name}`;
|
|
221
|
+
|
|
222
|
+
scores.push({
|
|
223
|
+
date: dateOnly,
|
|
224
|
+
game: gameName,
|
|
225
|
+
status: statusDescription,
|
|
226
|
+
eventName: event.name || event.shortName, // e.g., "UFC 324: Gaethje vs. Pimblett"
|
|
227
|
+
weightClass: competition.type?.abbreviation || '', // e.g., "Lightweight"
|
|
228
|
+
competitionId: competition.id,
|
|
229
|
+
competitors: transformedCompetitors,
|
|
230
|
+
// UFC-specific live data
|
|
231
|
+
ufcData: {
|
|
232
|
+
currentRound,
|
|
233
|
+
totalRounds,
|
|
234
|
+
clock,
|
|
235
|
+
fightState, // 'pre', 'in', 'post'
|
|
236
|
+
statusDetail,
|
|
237
|
+
statusShortDetail
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log(`[LiveScores:UFC] Successfully parsed ${scores.length} fights`);
|
|
244
|
+
|
|
245
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
246
|
+
// 🧪 MOCK UFC DATA - Development Testing
|
|
247
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
248
|
+
// In development, append mock UFC fights for testing the oracle.
|
|
249
|
+
// These match the stub events in sportsRoutes.js
|
|
250
|
+
//
|
|
251
|
+
// IMPORTANT: The winner mapping:
|
|
252
|
+
// - homeAway: 'home' = strHomeTeam (fighter 1)
|
|
253
|
+
// - homeAway: 'away' = strAwayTeam (fighter 2)
|
|
254
|
+
// - score: 1 = winner, score: 0 = loser
|
|
255
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
256
|
+
if (process.env.NODE_ENV === 'development') {
|
|
257
|
+
// Use same date calculation as sportsRoutes.js mock event (5 min from now)
|
|
258
|
+
const startTime = new Date(Date.now() + 5 * 60000);
|
|
259
|
+
const mockDate = startTime.toISOString().split('T')[0];
|
|
260
|
+
|
|
261
|
+
// Mock UFC Fight: Bautista vs Oliveira - Bautista (HOME) WINS
|
|
262
|
+
// strHomeTeam = "Mario Bautista" → homeAway: 'home' → score: 1 (winner)
|
|
263
|
+
// strAwayTeam = "Vinicius Oliveira" → homeAway: 'away' → score: 0 (loser)
|
|
264
|
+
// MUST match sportsRoutes.js mock event fighters!
|
|
265
|
+
scores.push({
|
|
266
|
+
date: mockDate,
|
|
267
|
+
game: "Mario Bautista vs Vinicius Oliveira",
|
|
268
|
+
status: "Final",
|
|
269
|
+
eventName: "[MOCK] UFC Fight Night: Bautista vs Oliveira",
|
|
270
|
+
weightClass: "Bantamweight",
|
|
271
|
+
competitionId: "mock-ufc-dev-test",
|
|
272
|
+
competitors: [
|
|
273
|
+
{
|
|
274
|
+
name: "Mario Bautista",
|
|
275
|
+
homeAway: "home",
|
|
276
|
+
score: 1, // WINNER
|
|
277
|
+
winner: true,
|
|
278
|
+
logo: "https://a.espncdn.com/combiner/i?img=/i/headshots/mma/players/full/4410868.png&w=350&h=254",
|
|
279
|
+
abbreviation: "Bautista",
|
|
280
|
+
record: "16-3-0"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "Vinicius Oliveira",
|
|
284
|
+
homeAway: "away",
|
|
285
|
+
score: 0, // LOSER
|
|
286
|
+
winner: false,
|
|
287
|
+
logo: "https://a.espncdn.com/combiner/i?img=/i/headshots/mma/players/full/4884877.png&w=350&h=254",
|
|
288
|
+
abbreviation: "Oliveira",
|
|
289
|
+
record: "22-4-0"
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
// UFC-specific data
|
|
293
|
+
ufcData: {
|
|
294
|
+
currentRound: 3,
|
|
295
|
+
totalRounds: 5,
|
|
296
|
+
clock: '',
|
|
297
|
+
fightState: 'post',
|
|
298
|
+
statusDetail: 'Decision - Unanimous',
|
|
299
|
+
statusShortDetail: 'Final - UD'
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
console.log(`[LiveScores:UFC] Added mock UFC fight for ${mockDate}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return scores;
|
|
307
|
+
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`[LiveScores:UFC] Failed to fetch UFC scores:`, error.message);
|
|
310
|
+
return [];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* GET /api/livescores
|
|
316
|
+
*/
|
|
317
|
+
const getAllLivescores = async (req, res) => {
|
|
318
|
+
try {
|
|
319
|
+
const results = {};
|
|
320
|
+
|
|
321
|
+
await Promise.all(
|
|
322
|
+
Object.entries(ESPN_URLS).map(async ([league, url]) => {
|
|
323
|
+
// Route UFC to isolated handler, all other sports use existing handler
|
|
324
|
+
if (league === 'UFC') {
|
|
325
|
+
results[league] = await fetchUFCScores(url);
|
|
326
|
+
} else {
|
|
327
|
+
results[league] = await fetchScoresForLeague(url, league);
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
res.json({ success: true, data: results });
|
|
333
|
+
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('[LiveScores] Error fetching all livescores:', error);
|
|
336
|
+
res.status(500).json({ success: false, error: 'Failed to fetch sports scores' });
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* GET /api/livescores/:league
|
|
342
|
+
*/
|
|
343
|
+
const getLivescoresByLeague = async (req, res) => {
|
|
344
|
+
const { league } = req.params;
|
|
345
|
+
const leagueUpper = league.toUpperCase();
|
|
346
|
+
const url = ESPN_URLS[leagueUpper];
|
|
347
|
+
|
|
348
|
+
if (!url) {
|
|
349
|
+
return res.status(400).json({
|
|
350
|
+
success: false,
|
|
351
|
+
error: `League "${league}" is not supported. Supported leagues: ${Object.keys(ESPN_URLS).join(', ')}`
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
// Route UFC to isolated handler, all other sports use existing handler
|
|
357
|
+
let scores;
|
|
358
|
+
if (leagueUpper === 'UFC') {
|
|
359
|
+
scores = await fetchUFCScores(url);
|
|
360
|
+
} else {
|
|
361
|
+
scores = await fetchScoresForLeague(url, leagueUpper);
|
|
362
|
+
}
|
|
363
|
+
res.json({ success: true, data: scores });
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(`[LiveScores] Error fetching ${leagueUpper} scores:`, error);
|
|
366
|
+
res.status(500).json({ success: false, error: `Failed to fetch ${leagueUpper} scores` });
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
module.exports = {
|
|
371
|
+
getAllLivescores,
|
|
372
|
+
getLivescoresByLeague,
|
|
373
|
+
fetchScoresForLeague,
|
|
374
|
+
fetchUFCScores,
|
|
375
|
+
ESPN_URLS,
|
|
376
|
+
};
|