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.
Files changed (304) hide show
  1. package/.claude/settings.local.json +280 -0
  2. package/CLAUDE.md +46 -0
  3. package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
  4. package/CURRENT_SESSION.md +171 -0
  5. package/CURRENT_SESSION_DRAW.md +516 -0
  6. package/MARCH_MADNESS_SURVIVOR.md +254 -0
  7. package/PANDA.md +166 -0
  8. package/Procfile +4 -0
  9. package/README.md +476 -0
  10. package/controllers/livescoresController.js +376 -0
  11. package/controllers/pickemController.js +554 -0
  12. package/controllers/survivorAdminController.js +887 -0
  13. package/controllers/survivorController.js +623 -0
  14. package/cron/oracleMonitor.js +77 -0
  15. package/cron/pickemOracleMonitor.js +73 -0
  16. package/data/jackpot-history.json +952 -0
  17. package/data/ncaaTeams.js +406 -0
  18. package/documentation/API_SECURITY_GUIDE.md +327 -0
  19. package/documentation/ARCADE_API.md +593 -0
  20. package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
  21. package/documentation/ARCADE_QUICKSTART.md +242 -0
  22. package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
  23. package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
  24. package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
  25. package/documentation/CLAIM_STATUS_FIX.md +67 -0
  26. package/documentation/CLI_TOOL_GUIDE.md +372 -0
  27. package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
  28. package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
  29. package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
  30. package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
  31. package/documentation/DATABASE_QUERIES.md +269 -0
  32. package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
  33. package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
  34. package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
  35. package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
  36. package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
  37. package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
  38. package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
  39. package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
  40. package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
  41. package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
  42. package/documentation/HEROKU_DEPLOYMENT.md +134 -0
  43. package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
  44. package/documentation/JACKPOT_API.md +521 -0
  45. package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
  46. package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
  47. package/documentation/JWT_QUICK_SETUP.md +268 -0
  48. package/documentation/JWT_TESTING_GUIDE.md +404 -0
  49. package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
  50. package/documentation/KEEPER_SETUP.md +206 -0
  51. package/documentation/KEEPER_STATE_MACHINE.md +423 -0
  52. package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
  53. package/documentation/LOCAL_VOTING_TEST.md +279 -0
  54. package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
  55. package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
  56. package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
  57. package/documentation/PNL_TRACKING_SETUP.md +189 -0
  58. package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
  59. package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
  60. package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
  61. package/documentation/QUICK_AUTH_SETUP.md +99 -0
  62. package/documentation/QUICK_DEPLOY.md +224 -0
  63. package/documentation/QUICK_FIX.md +114 -0
  64. package/documentation/QUICK_START.md +152 -0
  65. package/documentation/REFEREE_MODE_GUIDE.md +392 -0
  66. package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
  67. package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
  68. package/documentation/RUN_MIGRATION_NOW.md +39 -0
  69. package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
  70. package/documentation/SETUP_GUIDE.md +184 -0
  71. package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
  72. package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
  73. package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
  74. package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
  75. package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
  76. package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
  77. package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
  78. package/ecosystem.config.js +65 -0
  79. package/env.template +125 -0
  80. package/middleware/apiKeyAuth.js +136 -0
  81. package/middleware/authenticate.js +214 -0
  82. package/middleware/developerUserAuth.js +76 -0
  83. package/middleware/socketAuth.js +69 -0
  84. package/package.json +49 -0
  85. package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
  86. package/postman/Dubs-API-v1.postman_collection.json +205 -0
  87. package/postman/Dubs_Developer_API.postman_collection.json +662 -0
  88. package/postman/QUICKSTART.md +118 -0
  89. package/postman/QUICK_REFERENCE.md +246 -0
  90. package/postman/README.md +71 -0
  91. package/postman/VOTING_API_GUIDE.md +426 -0
  92. package/refactor/Animations.md +148 -0
  93. package/refactor/Chat.md +252 -0
  94. package/routes/actionsRoutes.js +699 -0
  95. package/routes/adminRoutes.js +370 -0
  96. package/routes/analyticsRoutes.js +1262 -0
  97. package/routes/arcadeRoutes.js +557 -0
  98. package/routes/authRoutes.js +2310 -0
  99. package/routes/avatarRoutes.js +85 -0
  100. package/routes/botRoutes.js +211 -0
  101. package/routes/chatRoutes.js +377 -0
  102. package/routes/cryptoPriceRoutes.js +105 -0
  103. package/routes/developerRoutes.js +4201 -0
  104. package/routes/deviceRoutes.js +214 -0
  105. package/routes/dmRoutes.js +167 -0
  106. package/routes/esportsRoutes.js +806 -0
  107. package/routes/exchangeRateRoutes.js +233 -0
  108. package/routes/gamesRoutes.js +3028 -0
  109. package/routes/jackpotRoutes.js +754 -0
  110. package/routes/keeperMonitoringRoutes.js +156 -0
  111. package/routes/keeperWebhookRoutes.js +466 -0
  112. package/routes/livescoresRoutes.js +31 -0
  113. package/routes/pickemAdminRoutes.js +199 -0
  114. package/routes/pickemRoutes.js +231 -0
  115. package/routes/playerStatsRoutes.js +147 -0
  116. package/routes/portfolioRoutes.js +217 -0
  117. package/routes/promoRoutes.js +418 -0
  118. package/routes/referralEarningsRoutes.js +392 -0
  119. package/routes/socialRoutes.js +459 -0
  120. package/routes/sportsRoutes.js +1271 -0
  121. package/routes/survivorAdminRoutes.js +345 -0
  122. package/routes/survivorRoutes.js +756 -0
  123. package/routes/uploadRoutes.js +256 -0
  124. package/routes/userProfileRoutes.js +244 -0
  125. package/routes/whatsNewRoutes.js +331 -0
  126. package/scripts/.claude/settings.local.json +15 -0
  127. package/scripts/README.md +170 -0
  128. package/scripts/RESTART_EVERYTHING.sh +104 -0
  129. package/scripts/add-claim-columns.sql +48 -0
  130. package/scripts/add-crypto-prices-cache.sql +27 -0
  131. package/scripts/add-exchange-rates-cache.sql +40 -0
  132. package/scripts/add-game-invite-column.sql +23 -0
  133. package/scripts/add-game-invite-notification.sql +33 -0
  134. package/scripts/add-game-invite-telegram-pref.sql +16 -0
  135. package/scripts/add-game-joined-notification.sql +16 -0
  136. package/scripts/add-game-joined-pref.js +40 -0
  137. package/scripts/add-game-joined-preference.sql +6 -0
  138. package/scripts/add-game-start-notifications.sql +41 -0
  139. package/scripts/add-notification-flags-to-games.sql +55 -0
  140. package/scripts/add-pending-game-dismissals.sql +19 -0
  141. package/scripts/add-preferred-currency.sql +34 -0
  142. package/scripts/add-winner-columns.js +61 -0
  143. package/scripts/add_mention_system.sql +53 -0
  144. package/scripts/add_payment_system.sql +96 -0
  145. package/scripts/add_sports_event_id_column.sql +22 -0
  146. package/scripts/analyze-cohort-data-heroku.js +276 -0
  147. package/scripts/analyze-cohort-data.js +295 -0
  148. package/scripts/analyze-prod-cohorts.sh +10 -0
  149. package/scripts/backfill-matchup-images.js +245 -0
  150. package/scripts/backfill-missing-signatures.js +175 -0
  151. package/scripts/backfill-referral-earnings.js +202 -0
  152. package/scripts/check-chat-schema.js +130 -0
  153. package/scripts/check-db.sh +14 -0
  154. package/scripts/check_oracle_in_game.js +54 -0
  155. package/scripts/cleanup-database.js +193 -0
  156. package/scripts/clear-notification-cache.js +85 -0
  157. package/scripts/convert-mnemonic.js +50 -0
  158. package/scripts/create-users-table.sql +44 -0
  159. package/scripts/debug-cohort-counts.js +248 -0
  160. package/scripts/debug-winner-calc.js +84 -0
  161. package/scripts/deploy-payment-system.sh +118 -0
  162. package/scripts/deploy-to-heroku.sh +63 -0
  163. package/scripts/diagnose-locked-round.js +143 -0
  164. package/scripts/dubs-cli.js +720 -0
  165. package/scripts/dump-account.js +65 -0
  166. package/scripts/find-vrf-offset.js +48 -0
  167. package/scripts/fix-chat-notifications-constraint.sql +122 -0
  168. package/scripts/fix-claim-columns.js +124 -0
  169. package/scripts/fix-constraint-now.js +44 -0
  170. package/scripts/fix-lock-timestamps.js +96 -0
  171. package/scripts/fix-locked-round.sh +126 -0
  172. package/scripts/fix-missing-badges.sql +91 -0
  173. package/scripts/fix-payment-notifications.sql +41 -0
  174. package/scripts/force-new-round.js +55 -0
  175. package/scripts/force-resolve-and-claim.js +278 -0
  176. package/scripts/important/README.md +115 -0
  177. package/scripts/important/authority-force-lock.js +197 -0
  178. package/scripts/important/authority-resolve-game.js +267 -0
  179. package/scripts/important/check-game-status.js +373 -0
  180. package/scripts/important/list-pending-games-by-version.js +270 -0
  181. package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
  182. package/scripts/initialize-jackpot.js +111 -0
  183. package/scripts/jackpot/.claude/settings.local.json +10 -0
  184. package/scripts/jackpot/force-reset.js +84 -0
  185. package/scripts/jackpot/initialize-mainnet.js +100 -0
  186. package/scripts/jackpot/keeper.js +742 -0
  187. package/scripts/jackpot/status.js +107 -0
  188. package/scripts/jackpot/update-round-duration.js +143 -0
  189. package/scripts/keeper-bot.js +112 -0
  190. package/scripts/list-pending-games.js +131 -0
  191. package/scripts/migrate-chat-v2.js +127 -0
  192. package/scripts/migrate-chat-winners.js +84 -0
  193. package/scripts/migrate-chat.sh +17 -0
  194. package/scripts/migrate-game-invite.js +83 -0
  195. package/scripts/migrate-heroku-game-notifications.sh +159 -0
  196. package/scripts/migrations/001_analytics_tables.sql +422 -0
  197. package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
  198. package/scripts/migrations/003_referral_earnings.sql +208 -0
  199. package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
  200. package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
  201. package/scripts/migrations/005_push_notifications.sql +55 -0
  202. package/scripts/migrations/006_add_draw_team_players.sql +28 -0
  203. package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
  204. package/scripts/migrations/007_add_gif_url.sql +8 -0
  205. package/scripts/migrations/008_add_connect4_columns.sql +139 -0
  206. package/scripts/migrations/008_add_pool_tracking.sql +22 -0
  207. package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
  208. package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
  209. package/scripts/migrations/011_create_developer_tables.sql +67 -0
  210. package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
  211. package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
  212. package/scripts/migrations/013_add_network_mode.sql +18 -0
  213. package/scripts/migrations/014_create_developer_app_users.sql +19 -0
  214. package/scripts/migrations/015_add_ui_config.sql +4 -0
  215. package/scripts/migrations/016_add_resolution_secret.sql +4 -0
  216. package/scripts/migrations/017_add_external_game_id.sql +3 -0
  217. package/scripts/migrations/018_create_pickem_tables.sql +115 -0
  218. package/scripts/migrations/019_expo_push_tokens.sql +19 -0
  219. package/scripts/migrations/create_whats_new_tables.sql +88 -0
  220. package/scripts/migrations/drop_live_games_tables.sql +34 -0
  221. package/scripts/open-jackpot-round.js +85 -0
  222. package/scripts/purge-all-data.sh +329 -0
  223. package/scripts/purge-all-data.sql +142 -0
  224. package/scripts/purge-heroku-data.sh +149 -0
  225. package/scripts/purge-heroku-data.sql +62 -0
  226. package/scripts/rebuild-heroku-database.sh +113 -0
  227. package/scripts/recover-funds.js +357 -0
  228. package/scripts/regenerate-epl-images.js +278 -0
  229. package/scripts/resize-s3-matchup-images.js +374 -0
  230. package/scripts/resolve-direct.js +88 -0
  231. package/scripts/resolve-mock-game.js +124 -0
  232. package/scripts/resolve-pickem-game.js +55 -0
  233. package/scripts/resolve-round-manual.js +83 -0
  234. package/scripts/resolve-stuck-game.js +382 -0
  235. package/scripts/resolve-stuck-round.js +42 -0
  236. package/scripts/run-connect4-migration.sh +16 -0
  237. package/scripts/run-mention-migration.sh +32 -0
  238. package/scripts/run-payment-migration.sh +51 -0
  239. package/scripts/run-preferred-currency-migration.sh +31 -0
  240. package/scripts/run-referral-earnings-migration.sh +32 -0
  241. package/scripts/run-survivor-outcome-migration.sh +16 -0
  242. package/scripts/seed-test-users.js +346 -0
  243. package/scripts/setup-auth-tables.js +78 -0
  244. package/scripts/setup-complete-database.sql +992 -0
  245. package/scripts/setup-database-fresh.sh +359 -0
  246. package/scripts/setup-heroku-keeper.sh +48 -0
  247. package/scripts/setup-keeper-database.js +83 -0
  248. package/scripts/setup-keeper-state-db.sql +110 -0
  249. package/scripts/setup-oracle.sh +39 -0
  250. package/scripts/setup-pnl-tracking.js +111 -0
  251. package/scripts/start-devnet.sh +14 -0
  252. package/scripts/test-arcade-devnet.sh +160 -0
  253. package/scripts/test-arcade-match.sh +109 -0
  254. package/scripts/test-automatic-mode.sh +239 -0
  255. package/scripts/test-connect4-cancel-claim.js +370 -0
  256. package/scripts/test-connect4-e2e.js +369 -0
  257. package/scripts/test-connect4-resolve.js +369 -0
  258. package/scripts/test-game-state-endpoint.js +136 -0
  259. package/scripts/test-invite-notification.js +86 -0
  260. package/scripts/test-jackpot-api.sh +71 -0
  261. package/scripts/test-poll-confirmation.js +267 -0
  262. package/scripts/test-resolve-game.js +271 -0
  263. package/scripts/test-resolve-signature.js +223 -0
  264. package/scripts/test-signature-preservation.js +124 -0
  265. package/scripts/test-state-machine.js +291 -0
  266. package/scripts/test-webhook-receiver.js +60 -0
  267. package/scripts/update-notification-constraint.js +52 -0
  268. package/scripts/verify-account-layout.js +145 -0
  269. package/scripts/verify-winner-algorithm.js +278 -0
  270. package/server.js +5259 -0
  271. package/services/arcadeMatchService.js +763 -0
  272. package/services/automaticGameOracle.js +1596 -0
  273. package/services/chatService.js +1612 -0
  274. package/services/connect4GameService.js +1049 -0
  275. package/services/connect4NotificationService.js +374 -0
  276. package/services/cryptoPriceService.js +223 -0
  277. package/services/customGameResolver.js +260 -0
  278. package/services/db.js +79 -0
  279. package/services/directMessageService.js +389 -0
  280. package/services/discordNotifications.js +160 -0
  281. package/services/exchangeRateService.js +289 -0
  282. package/services/expoPushService.js +314 -0
  283. package/services/gamesCacheService.js +539 -0
  284. package/services/jackpotHistory.js +331 -0
  285. package/services/jackpotService.js +856 -0
  286. package/services/keeperStateService.js +355 -0
  287. package/services/matchupImageService.js +591 -0
  288. package/services/notificationCacheService.js +407 -0
  289. package/services/pickemOracle.js +440 -0
  290. package/services/playerStatsService.js +389 -0
  291. package/services/portfolioService.js +555 -0
  292. package/services/promoService.js +757 -0
  293. package/services/promoTreasuryService.js +239 -0
  294. package/services/pushNotifications.js +353 -0
  295. package/services/redisService.js +422 -0
  296. package/services/referralEarningsService.js +728 -0
  297. package/services/s3Service.js +396 -0
  298. package/services/socialService.js +1202 -0
  299. package/services/survivorOracle.js +469 -0
  300. package/services/survivorSimulator.js +475 -0
  301. package/services/telegramNotifications.js +461 -0
  302. package/services/userProfileStatsService.js +1185 -0
  303. package/services/whatsNewService.js +388 -0
  304. package/utils/urlHelper.js +95 -0
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Survivor Pool Routes
3
+ * API endpoints for March Madness Survivor Pool feature
4
+ */
5
+
6
+ const express = require('express');
7
+ const router = express.Router();
8
+ const survivorController = require('../controllers/survivorController');
9
+ const { pool } = require('../services/db');
10
+ const { authenticate } = require('../middleware/authenticate');
11
+
12
+ // Socket.IO for real-time updates
13
+ let io = null;
14
+
15
+ // Inject Socket.IO instance
16
+ router.setSocketIO = (ioInstance) => {
17
+ io = ioInstance;
18
+ console.log('[Survivor] Socket.IO injected');
19
+ };
20
+
21
+ // ============================================
22
+ // POOL MANAGEMENT
23
+ // ============================================
24
+
25
+ /**
26
+ * POST /api/survivor/pools
27
+ * Create a new survivor pool (admin only)
28
+ */
29
+ router.post('/pools', async (req, res) => {
30
+ try {
31
+ const { name, year, buyInLamports, roundDeadline, allowSameTeamTwice, solanaGameId } = req.body;
32
+
33
+ // Get creator from auth header or body
34
+ const createdBy = req.body.createdBy || req.headers['x-wallet-address'];
35
+
36
+ if (!name || !year || !buyInLamports) {
37
+ return res.status(400).json({
38
+ success: false,
39
+ error: 'Missing required fields: name, year, buyInLamports'
40
+ });
41
+ }
42
+
43
+ const pool = await survivorController.createPool({
44
+ name,
45
+ year,
46
+ buyInLamports,
47
+ roundDeadline,
48
+ allowSameTeamTwice,
49
+ solanaGameId,
50
+ createdBy
51
+ });
52
+
53
+ res.json({
54
+ success: true,
55
+ pool
56
+ });
57
+ } catch (error) {
58
+ console.error('[Survivor] Error creating pool:', error.message);
59
+ res.status(500).json({
60
+ success: false,
61
+ error: error.message
62
+ });
63
+ }
64
+ });
65
+
66
+ /**
67
+ * GET /api/survivor/pools
68
+ * List all survivor pools
69
+ */
70
+ router.get('/pools', async (req, res) => {
71
+ try {
72
+ const { status, year } = req.query;
73
+ const pools = await survivorController.getPools({ status, year: year ? parseInt(year) : undefined });
74
+
75
+ res.json({
76
+ success: true,
77
+ pools
78
+ });
79
+ } catch (error) {
80
+ console.error('[Survivor] Error listing pools:', error.message);
81
+ res.status(500).json({
82
+ success: false,
83
+ error: error.message
84
+ });
85
+ }
86
+ });
87
+
88
+ /**
89
+ * GET /api/survivor/pools/:id
90
+ * Get pool details with leaderboard
91
+ */
92
+ router.get('/pools/:id', async (req, res) => {
93
+ try {
94
+ const poolId = parseInt(req.params.id);
95
+ const survivorPool = await survivorController.getPoolById(poolId);
96
+
97
+ if (!survivorPool) {
98
+ return res.status(404).json({
99
+ success: false,
100
+ error: 'Pool not found'
101
+ });
102
+ }
103
+
104
+ // Get stats
105
+ const stats = await survivorController.getPoolStats(poolId);
106
+
107
+ // Get alive entries for leaderboard
108
+ const aliveEntries = await survivorController.getAliveEntries(poolId);
109
+
110
+ res.json({
111
+ success: true,
112
+ pool: survivorPool,
113
+ stats,
114
+ leaderboard: aliveEntries.slice(0, 20) // Top 20 for preview
115
+ });
116
+ } catch (error) {
117
+ console.error('[Survivor] Error getting pool:', error.message);
118
+ res.status(500).json({
119
+ success: false,
120
+ error: error.message
121
+ });
122
+ }
123
+ });
124
+
125
+ /**
126
+ * PATCH /api/survivor/pools/:id
127
+ * Update pool settings (admin only)
128
+ */
129
+ router.patch('/pools/:id', async (req, res) => {
130
+ try {
131
+ const poolId = parseInt(req.params.id);
132
+ const updates = req.body;
133
+
134
+ const updatedPool = await survivorController.updatePool(poolId, updates);
135
+
136
+ if (!updatedPool) {
137
+ return res.status(404).json({
138
+ success: false,
139
+ error: 'Pool not found'
140
+ });
141
+ }
142
+
143
+ // Emit update via Socket.IO if available
144
+ if (io) {
145
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
146
+ }
147
+
148
+ res.json({
149
+ success: true,
150
+ pool: updatedPool
151
+ });
152
+ } catch (error) {
153
+ console.error('[Survivor] Error updating pool:', error.message);
154
+ res.status(500).json({
155
+ success: false,
156
+ error: error.message
157
+ });
158
+ }
159
+ });
160
+
161
+ // ============================================
162
+ // ENTRY MANAGEMENT
163
+ // ============================================
164
+
165
+ /**
166
+ * POST /api/survivor/pools/:id/join
167
+ * Join a survivor pool
168
+ */
169
+ router.post('/pools/:id/join', authenticate, async (req, res) => {
170
+ try {
171
+ const poolId = parseInt(req.params.id);
172
+ const { userId, walletAddress } = req.user;
173
+ const { txSignature } = req.body;
174
+
175
+ const entry = await survivorController.joinPool({
176
+ poolId,
177
+ userId,
178
+ walletAddress,
179
+ txSignature
180
+ });
181
+
182
+ // Emit join event via Socket.IO
183
+ if (io) {
184
+ io.emit('survivor:entry_joined', { poolId, entry });
185
+ }
186
+
187
+ res.json({
188
+ success: true,
189
+ entry
190
+ });
191
+ } catch (error) {
192
+ console.error('[Survivor] Error joining pool:', error.message);
193
+ res.status(400).json({
194
+ success: false,
195
+ error: error.message
196
+ });
197
+ }
198
+ });
199
+
200
+ /**
201
+ * GET /api/survivor/pools/:id/my-entry
202
+ * Get current user's entry and picks
203
+ */
204
+ router.get('/pools/:id/my-entry', authenticate, async (req, res) => {
205
+ try {
206
+ const poolId = parseInt(req.params.id);
207
+ const { userId } = req.user;
208
+
209
+ const entryWithPicks = await survivorController.getUserEntry(poolId, userId);
210
+
211
+ if (!entryWithPicks) {
212
+ return res.status(404).json({
213
+ success: false,
214
+ error: 'Entry not found - user has not joined this pool'
215
+ });
216
+ }
217
+
218
+ // Get pool info too
219
+ const survivorPool = await survivorController.getPoolById(poolId);
220
+
221
+ // Extract picks from entry and format response
222
+ const { picks, ...entry } = entryWithPicks;
223
+
224
+ res.json({
225
+ success: true,
226
+ entry,
227
+ picks: picks || [],
228
+ pool: survivorPool
229
+ });
230
+ } catch (error) {
231
+ console.error('[Survivor] Error getting user entry:', error.message);
232
+ res.status(500).json({
233
+ success: false,
234
+ error: error.message
235
+ });
236
+ }
237
+ });
238
+
239
+ // ============================================
240
+ // PICKS
241
+ // ============================================
242
+
243
+ /**
244
+ * GET /api/survivor/pools/:id/teams
245
+ * Get available teams for current round
246
+ */
247
+ router.get('/pools/:id/teams', authenticate, async (req, res) => {
248
+ try {
249
+ const poolId = parseInt(req.params.id);
250
+ const { userId } = req.user;
251
+
252
+ const teamsData = await survivorController.getAvailableTeams(poolId, userId);
253
+
254
+ res.json({
255
+ success: true,
256
+ ...teamsData
257
+ });
258
+ } catch (error) {
259
+ console.error('[Survivor] Error getting teams:', error.message);
260
+ res.status(400).json({
261
+ success: false,
262
+ error: error.message
263
+ });
264
+ }
265
+ });
266
+
267
+ /**
268
+ * POST /api/survivor/pools/:id/pick
269
+ * Submit pick for current round
270
+ */
271
+ router.post('/pools/:id/pick', authenticate, async (req, res) => {
272
+ try {
273
+ const poolId = parseInt(req.params.id);
274
+ const { userId } = req.user;
275
+ const { teamId, teamName, teamSeed, teamLogo, espnGameId, opponentName, opponentSeed } = req.body;
276
+
277
+ if (!teamId || !teamName) {
278
+ return res.status(400).json({
279
+ success: false,
280
+ error: 'Missing required fields: teamId, teamName'
281
+ });
282
+ }
283
+
284
+ const pick = await survivorController.submitPick({
285
+ poolId,
286
+ userId,
287
+ teamId,
288
+ teamName,
289
+ teamSeed,
290
+ teamLogo,
291
+ espnGameId,
292
+ opponentName,
293
+ opponentSeed
294
+ });
295
+
296
+ // Emit pick event via Socket.IO
297
+ if (io) {
298
+ io.emit('survivor:pick_submitted', { poolId, userId, pick });
299
+ }
300
+
301
+ res.json({
302
+ success: true,
303
+ pick
304
+ });
305
+ } catch (error) {
306
+ console.error('[Survivor] Error submitting pick:', error.message);
307
+ res.status(400).json({
308
+ success: false,
309
+ error: error.message
310
+ });
311
+ }
312
+ });
313
+
314
+ /**
315
+ * GET /api/survivor/pools/:id/picks
316
+ * Get all picks for user
317
+ */
318
+ router.get('/pools/:id/picks', authenticate, async (req, res) => {
319
+ try {
320
+ const poolId = parseInt(req.params.id);
321
+ const { userId } = req.user;
322
+
323
+ const picks = await survivorController.getUserPicks(poolId, userId);
324
+
325
+ res.json({
326
+ success: true,
327
+ picks
328
+ });
329
+ } catch (error) {
330
+ console.error('[Survivor] Error getting picks:', error.message);
331
+ res.status(500).json({
332
+ success: false,
333
+ error: error.message
334
+ });
335
+ }
336
+ });
337
+
338
+ // ============================================
339
+ // LEADERBOARD
340
+ // ============================================
341
+
342
+ /**
343
+ * GET /api/survivor/pools/:id/alive
344
+ * Get surviving entries
345
+ */
346
+ router.get('/pools/:id/alive', async (req, res) => {
347
+ try {
348
+ const poolId = parseInt(req.params.id);
349
+ const entries = await survivorController.getAliveEntries(poolId);
350
+
351
+ res.json({
352
+ success: true,
353
+ count: entries.length,
354
+ entries
355
+ });
356
+ } catch (error) {
357
+ console.error('[Survivor] Error getting alive entries:', error.message);
358
+ res.status(500).json({
359
+ success: false,
360
+ error: error.message
361
+ });
362
+ }
363
+ });
364
+
365
+ /**
366
+ * GET /api/survivor/pools/:id/eliminated
367
+ * Get eliminated entries
368
+ */
369
+ router.get('/pools/:id/eliminated', async (req, res) => {
370
+ try {
371
+ const poolId = parseInt(req.params.id);
372
+ const entries = await survivorController.getEliminatedEntries(poolId);
373
+
374
+ res.json({
375
+ success: true,
376
+ count: entries.length,
377
+ entries
378
+ });
379
+ } catch (error) {
380
+ console.error('[Survivor] Error getting eliminated entries:', error.message);
381
+ res.status(500).json({
382
+ success: false,
383
+ error: error.message
384
+ });
385
+ }
386
+ });
387
+
388
+ /**
389
+ * GET /api/survivor/pools/:id/stats
390
+ * Get pool statistics
391
+ */
392
+ router.get('/pools/:id/stats', async (req, res) => {
393
+ try {
394
+ const poolId = parseInt(req.params.id);
395
+ const stats = await survivorController.getPoolStats(poolId);
396
+
397
+ res.json({
398
+ success: true,
399
+ stats
400
+ });
401
+ } catch (error) {
402
+ console.error('[Survivor] Error getting stats:', error.message);
403
+ res.status(500).json({
404
+ success: false,
405
+ error: error.message
406
+ });
407
+ }
408
+ });
409
+
410
+ // ============================================
411
+ // ADMIN / ORACLE ENDPOINTS
412
+ // ============================================
413
+
414
+ /**
415
+ * POST /api/survivor/pools/:id/games
416
+ * Import tournament games (admin)
417
+ */
418
+ router.post('/pools/:id/games', async (req, res) => {
419
+ try {
420
+ const poolId = parseInt(req.params.id);
421
+ const { games } = req.body;
422
+
423
+ if (!games || !Array.isArray(games)) {
424
+ return res.status(400).json({
425
+ success: false,
426
+ error: 'Missing required field: games (array)'
427
+ });
428
+ }
429
+
430
+ const importedGames = await survivorController.importTournamentGames(poolId, games);
431
+
432
+ res.json({
433
+ success: true,
434
+ count: importedGames.length,
435
+ games: importedGames
436
+ });
437
+ } catch (error) {
438
+ console.error('[Survivor] Error importing games:', error.message);
439
+ res.status(500).json({
440
+ success: false,
441
+ error: error.message
442
+ });
443
+ }
444
+ });
445
+
446
+ /**
447
+ * GET /api/survivor/pools/:id/games
448
+ * Get tournament games for a pool
449
+ */
450
+ router.get('/pools/:id/games', async (req, res) => {
451
+ try {
452
+ const poolId = parseInt(req.params.id);
453
+ const { round } = req.query;
454
+
455
+ console.log(`[Survivor] GET /games - poolId: ${poolId}, round: ${round || 'all'}`);
456
+
457
+ let query = 'SELECT * FROM survivor_tournament_games WHERE pool_id = $1';
458
+ const params = [poolId];
459
+
460
+ if (round) {
461
+ params.push(parseInt(round));
462
+ query += ` AND round = $${params.length}`;
463
+ }
464
+
465
+ query += ' ORDER BY round, game_date, id';
466
+
467
+ const result = await pool.query(query, params);
468
+
469
+ console.log(`[Survivor] GET /games - found ${result.rows.length} games`);
470
+
471
+ res.json({
472
+ success: true,
473
+ count: result.rows.length,
474
+ games: result.rows
475
+ });
476
+ } catch (error) {
477
+ console.error('[Survivor] Error getting games:', error.message);
478
+ res.status(500).json({
479
+ success: false,
480
+ error: error.message
481
+ });
482
+ }
483
+ });
484
+
485
+ /**
486
+ * POST /api/survivor/pools/:id/resolve
487
+ * Resolve current round (oracle)
488
+ * Called by survivorOracle service after games complete
489
+ */
490
+ router.post('/pools/:id/resolve', async (req, res) => {
491
+ try {
492
+ const poolId = parseInt(req.params.id);
493
+
494
+ // This endpoint is called by the oracle service
495
+ // Import the oracle and trigger resolution
496
+ const survivorOracle = require('../services/survivorOracle');
497
+ const result = await survivorOracle.resolveCurrentRound(poolId);
498
+
499
+ // Emit round resolved event
500
+ if (io) {
501
+ io.emit('survivor:round_resolved', { poolId, result });
502
+ }
503
+
504
+ res.json({
505
+ success: true,
506
+ result
507
+ });
508
+ } catch (error) {
509
+ console.error('[Survivor] Error resolving round:', error.message);
510
+ res.status(500).json({
511
+ success: false,
512
+ error: error.message
513
+ });
514
+ }
515
+ });
516
+
517
+ // ============================================
518
+ // DEV / SIMULATION ENDPOINTS (development only)
519
+ // ============================================
520
+
521
+ // Only enable in development
522
+ if (process.env.NODE_ENV === 'development' || process.env.ENABLE_SURVIVOR_DEV === 'true') {
523
+ const survivorSimulator = require('../services/survivorSimulator');
524
+
525
+ /**
526
+ * POST /api/survivor/dev/pools/:id/generate-bracket
527
+ * Generate mock tournament bracket for testing
528
+ */
529
+ router.post('/dev/pools/:id/generate-bracket', async (req, res) => {
530
+ try {
531
+ const poolId = parseInt(req.params.id);
532
+ const games = await survivorSimulator.generateTournamentBracket(poolId);
533
+
534
+ // Emit pool updated for real-time bracket refresh
535
+ if (io) {
536
+ const updatedPool = await survivorController.getPoolById(poolId);
537
+ if (updatedPool) {
538
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
539
+ }
540
+ }
541
+
542
+ res.json({
543
+ success: true,
544
+ message: `Generated ${games.length} tournament games`,
545
+ count: games.length
546
+ });
547
+ } catch (error) {
548
+ console.error('[Survivor:Dev] Error generating bracket:', error.message);
549
+ res.status(500).json({
550
+ success: false,
551
+ error: error.message
552
+ });
553
+ }
554
+ });
555
+
556
+ /**
557
+ * POST /api/survivor/dev/pools/:id/simulate-round
558
+ * Simulate current round with random results
559
+ */
560
+ router.post('/dev/pools/:id/simulate-round', async (req, res) => {
561
+ try {
562
+ const poolId = parseInt(req.params.id);
563
+ const { upsetChance = 0.2, favorChalk = false } = req.body;
564
+
565
+ const result = await survivorSimulator.simulateRound(poolId, { upsetChance, favorChalk });
566
+
567
+ // Emit round resolved event for real-time updates
568
+ if (io && result.resolution) {
569
+ io.emit('survivor:round_resolved', { poolId, result: result.resolution });
570
+
571
+ // Also emit pool updated with latest pool state
572
+ const updatedPool = await survivorController.getPoolById(poolId);
573
+ if (updatedPool) {
574
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
575
+ }
576
+ }
577
+
578
+ res.json({
579
+ success: true,
580
+ result
581
+ });
582
+ } catch (error) {
583
+ console.error('[Survivor:Dev] Error simulating round:', error.message);
584
+ res.status(500).json({
585
+ success: false,
586
+ error: error.message
587
+ });
588
+ }
589
+ });
590
+
591
+ /**
592
+ * POST /api/survivor/dev/pools/:id/generate-entries
593
+ * Generate test entries with random picks
594
+ */
595
+ router.post('/dev/pools/:id/generate-entries', async (req, res) => {
596
+ try {
597
+ const poolId = parseInt(req.params.id);
598
+ const { count = 10 } = req.body;
599
+
600
+ const result = await survivorSimulator.generateTestEntries(poolId, count);
601
+
602
+ res.json({
603
+ success: true,
604
+ result
605
+ });
606
+ } catch (error) {
607
+ console.error('[Survivor:Dev] Error generating entries:', error.message);
608
+ res.status(500).json({
609
+ success: false,
610
+ error: error.message
611
+ });
612
+ }
613
+ });
614
+
615
+ /**
616
+ * POST /api/survivor/dev/pools/:id/set-deadline
617
+ * Set round deadline to N minutes from now
618
+ */
619
+ router.post('/dev/pools/:id/set-deadline', async (req, res) => {
620
+ try {
621
+ const poolId = parseInt(req.params.id);
622
+ const { minutes = 5 } = req.body;
623
+
624
+ const result = await survivorSimulator.setDeadline(poolId, minutes);
625
+
626
+ // Emit pool updated for real-time refresh
627
+ if (io) {
628
+ const updatedPool = await survivorController.getPoolById(poolId);
629
+ if (updatedPool) {
630
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
631
+ }
632
+ }
633
+
634
+ res.json({
635
+ success: true,
636
+ result
637
+ });
638
+ } catch (error) {
639
+ console.error('[Survivor:Dev] Error setting deadline:', error.message);
640
+ res.status(500).json({
641
+ success: false,
642
+ error: error.message
643
+ });
644
+ }
645
+ });
646
+
647
+ /**
648
+ * POST /api/survivor/dev/pools/:id/reset
649
+ * Reset pool to round 1
650
+ * Body: { deleteEntries: boolean } - if true, also delete all entries
651
+ */
652
+ router.post('/dev/pools/:id/reset', async (req, res) => {
653
+ try {
654
+ const poolId = parseInt(req.params.id);
655
+ const { deleteEntries = false } = req.body;
656
+
657
+ const result = await survivorSimulator.resetPool(poolId, { deleteEntries });
658
+
659
+ // Emit pool updated for real-time refresh
660
+ if (io) {
661
+ const updatedPool = await survivorController.getPoolById(poolId);
662
+ if (updatedPool) {
663
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
664
+ }
665
+ }
666
+
667
+ res.json({
668
+ success: true,
669
+ result
670
+ });
671
+ } catch (error) {
672
+ console.error('[Survivor:Dev] Error resetting pool:', error.message);
673
+ res.status(500).json({
674
+ success: false,
675
+ error: error.message
676
+ });
677
+ }
678
+ });
679
+
680
+ /**
681
+ * POST /api/survivor/dev/pools/:id/activate
682
+ * Activate pool and set to round 2
683
+ */
684
+ router.post('/dev/pools/:id/activate', async (req, res) => {
685
+ try {
686
+ const poolId = parseInt(req.params.id);
687
+ const result = await survivorSimulator.activatePool(poolId);
688
+
689
+ // Emit pool updated for real-time refresh
690
+ if (io) {
691
+ const updatedPool = await survivorController.getPoolById(poolId);
692
+ if (updatedPool) {
693
+ io.emit('survivor:pool_updated', { poolId, pool: updatedPool });
694
+ }
695
+ }
696
+
697
+ res.json({
698
+ success: true,
699
+ result
700
+ });
701
+ } catch (error) {
702
+ console.error('[Survivor:Dev] Error activating pool:', error.message);
703
+ res.status(500).json({
704
+ success: false,
705
+ error: error.message
706
+ });
707
+ }
708
+ });
709
+
710
+ /**
711
+ * DELETE /api/survivor/dev/pools/:id/test-entries
712
+ * Delete test entries (cleanup)
713
+ */
714
+ router.delete('/dev/pools/:id/test-entries', async (req, res) => {
715
+ try {
716
+ const poolId = parseInt(req.params.id);
717
+ const result = await survivorSimulator.deleteTestEntries(poolId);
718
+
719
+ res.json({
720
+ success: true,
721
+ result
722
+ });
723
+ } catch (error) {
724
+ console.error('[Survivor:Dev] Error deleting test entries:', error.message);
725
+ res.status(500).json({
726
+ success: false,
727
+ error: error.message
728
+ });
729
+ }
730
+ });
731
+
732
+ /**
733
+ * GET /api/survivor/dev/teams
734
+ * Get list of mock tournament teams
735
+ */
736
+ router.get('/dev/teams', async (req, res) => {
737
+ try {
738
+ const { MOCK_TEAMS } = require('../services/survivorSimulator');
739
+ res.json({
740
+ success: true,
741
+ count: MOCK_TEAMS.length,
742
+ teams: MOCK_TEAMS
743
+ });
744
+ } catch (error) {
745
+ console.error('[Survivor:Dev] Error getting teams:', error.message);
746
+ res.status(500).json({
747
+ success: false,
748
+ error: error.message
749
+ });
750
+ }
751
+ });
752
+
753
+ console.log('[Survivor] Dev/simulation endpoints enabled');
754
+ }
755
+
756
+ module.exports = router;