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,475 @@
1
+ /**
2
+ * Survivor Pool Simulator Service
3
+ * Development/testing tools for March Madness Survivor Pool
4
+ *
5
+ * Provides mock tournament data and simulation controls for testing
6
+ * without waiting for actual tournament games.
7
+ */
8
+
9
+ const { pool } = require('./db');
10
+ const survivorController = require('../controllers/survivorController');
11
+
12
+ // ============================================
13
+ // MOCK TOURNAMENT DATA
14
+ // ============================================
15
+
16
+ // ESPN logo URL helper - uses ESPN's CDN
17
+ const espnLogo = (espnId) => `https://a.espncdn.com/i/teamlogos/ncaa/500/${espnId}.png`;
18
+
19
+ // 68 teams for March Madness (64 + 4 First Four) with ESPN team IDs for logos
20
+ const MOCK_TEAMS = [
21
+ // EAST Region
22
+ { id: 'e1', name: 'Duke', seed: 1, region: 'EAST', logo: espnLogo(150) },
23
+ { id: 'e2', name: 'Alabama', seed: 2, region: 'EAST', logo: espnLogo(333) },
24
+ { id: 'e3', name: 'Wisconsin', seed: 3, region: 'EAST', logo: espnLogo(275) },
25
+ { id: 'e4', name: 'Arizona', seed: 4, region: 'EAST', logo: espnLogo(12) },
26
+ { id: 'e5', name: 'Oregon', seed: 5, region: 'EAST', logo: espnLogo(2483) },
27
+ { id: 'e6', name: 'BYU', seed: 6, region: 'EAST', logo: espnLogo(252) },
28
+ { id: 'e7', name: 'St. Marys', seed: 7, region: 'EAST', logo: espnLogo(2608) },
29
+ { id: 'e8', name: 'Louisville', seed: 8, region: 'EAST', logo: espnLogo(97) },
30
+ { id: 'e9', name: 'Creighton', seed: 9, region: 'EAST', logo: espnLogo(156) },
31
+ { id: 'e10', name: 'Colorado', seed: 10, region: 'EAST', logo: espnLogo(38) },
32
+ { id: 'e11', name: 'Memphis', seed: 11, region: 'EAST', logo: espnLogo(235) },
33
+ { id: 'e12', name: 'UC San Diego', seed: 12, region: 'EAST', logo: espnLogo(301) },
34
+ { id: 'e13', name: 'Yale', seed: 13, region: 'EAST', logo: espnLogo(43) },
35
+ { id: 'e14', name: 'Lipscomb', seed: 14, region: 'EAST', logo: espnLogo(288) },
36
+ { id: 'e15', name: 'Robert Morris', seed: 15, region: 'EAST', logo: espnLogo(2523) },
37
+ { id: 'e16', name: 'American', seed: 16, region: 'EAST', logo: espnLogo(44) },
38
+
39
+ // WEST Region
40
+ { id: 'w1', name: 'Florida', seed: 1, region: 'WEST', logo: espnLogo(57) },
41
+ { id: 'w2', name: 'St. Johns', seed: 2, region: 'WEST', logo: espnLogo(2599) },
42
+ { id: 'w3', name: 'Texas Tech', seed: 3, region: 'WEST', logo: espnLogo(2641) },
43
+ { id: 'w4', name: 'Maryland', seed: 4, region: 'WEST', logo: espnLogo(120) },
44
+ { id: 'w5', name: 'Memphis State', seed: 5, region: 'WEST', logo: espnLogo(235) },
45
+ { id: 'w6', name: 'Missouri', seed: 6, region: 'WEST', logo: espnLogo(142) },
46
+ { id: 'w7', name: 'Kansas', seed: 7, region: 'WEST', logo: espnLogo(2305) },
47
+ { id: 'w8', name: 'UConn', seed: 8, region: 'WEST', logo: espnLogo(41) },
48
+ { id: 'w9', name: 'Oklahoma', seed: 9, region: 'WEST', logo: espnLogo(201) },
49
+ { id: 'w10', name: 'Arkansas', seed: 10, region: 'WEST', logo: espnLogo(8) },
50
+ { id: 'w11', name: 'Drake', seed: 11, region: 'WEST', logo: espnLogo(2181) },
51
+ { id: 'w12', name: 'Grand Canyon', seed: 12, region: 'WEST', logo: espnLogo(2253) },
52
+ { id: 'w13', name: 'UNC Wilmington', seed: 13, region: 'WEST', logo: espnLogo(350) },
53
+ { id: 'w14', name: 'North Dakota', seed: 14, region: 'WEST', logo: espnLogo(155) },
54
+ { id: 'w15', name: 'Omaha', seed: 15, region: 'WEST', logo: espnLogo(2437) },
55
+ { id: 'w16', name: 'Norfolk State', seed: 16, region: 'WEST', logo: espnLogo(2450) },
56
+
57
+ // SOUTH Region
58
+ { id: 's1', name: 'Auburn', seed: 1, region: 'SOUTH', logo: espnLogo(2) },
59
+ { id: 's2', name: 'Michigan State', seed: 2, region: 'SOUTH', logo: espnLogo(127) },
60
+ { id: 's3', name: 'Iowa State', seed: 3, region: 'SOUTH', logo: espnLogo(66) },
61
+ { id: 's4', name: 'Texas A&M', seed: 4, region: 'SOUTH', logo: espnLogo(245) },
62
+ { id: 's5', name: 'Michigan', seed: 5, region: 'SOUTH', logo: espnLogo(130) },
63
+ { id: 's6', name: 'Ole Miss', seed: 6, region: 'SOUTH', logo: espnLogo(145) },
64
+ { id: 's7', name: 'Marquette', seed: 7, region: 'SOUTH', logo: espnLogo(269) },
65
+ { id: 's8', name: 'Boise State', seed: 8, region: 'SOUTH', logo: espnLogo(68) },
66
+ { id: 's9', name: 'Georgia', seed: 9, region: 'SOUTH', logo: espnLogo(61) },
67
+ { id: 's10', name: 'New Mexico', seed: 10, region: 'SOUTH', logo: espnLogo(167) },
68
+ { id: 's11', name: 'Vanderbilt', seed: 11, region: 'SOUTH', logo: espnLogo(238) },
69
+ { id: 's12', name: 'Liberty', seed: 12, region: 'SOUTH', logo: espnLogo(2335) },
70
+ { id: 's13', name: 'Akron', seed: 13, region: 'SOUTH', logo: espnLogo(2006) },
71
+ { id: 's14', name: 'Montana', seed: 14, region: 'SOUTH', logo: espnLogo(149) },
72
+ { id: 's15', name: 'Wofford', seed: 15, region: 'SOUTH', logo: espnLogo(2747) },
73
+ { id: 's16', name: 'Alabama State', seed: 16, region: 'SOUTH', logo: espnLogo(2011) },
74
+
75
+ // MIDWEST Region
76
+ { id: 'm1', name: 'Houston', seed: 1, region: 'MIDWEST', logo: espnLogo(248) },
77
+ { id: 'm2', name: 'Tennessee', seed: 2, region: 'MIDWEST', logo: espnLogo(2633) },
78
+ { id: 'm3', name: 'Kentucky', seed: 3, region: 'MIDWEST', logo: espnLogo(96) },
79
+ { id: 'm4', name: 'Purdue', seed: 4, region: 'MIDWEST', logo: espnLogo(2509) },
80
+ { id: 'm5', name: 'Clemson', seed: 5, region: 'MIDWEST', logo: espnLogo(228) },
81
+ { id: 'm6', name: 'Illinois', seed: 6, region: 'MIDWEST', logo: espnLogo(356) },
82
+ { id: 'm7', name: 'UCLA', seed: 7, region: 'MIDWEST', logo: espnLogo(26) },
83
+ { id: 'm8', name: 'Gonzaga', seed: 8, region: 'MIDWEST', logo: espnLogo(2250) },
84
+ { id: 'm9', name: 'Baylor', seed: 9, region: 'MIDWEST', logo: espnLogo(239) },
85
+ { id: 'm10', name: 'Texas', seed: 10, region: 'MIDWEST', logo: espnLogo(251) },
86
+ { id: 'm11', name: 'NC State', seed: 11, region: 'MIDWEST', logo: espnLogo(152) },
87
+ { id: 'm12', name: 'McNeese State', seed: 12, region: 'MIDWEST', logo: espnLogo(2377) },
88
+ { id: 'm13', name: 'Vermont', seed: 13, region: 'MIDWEST', logo: espnLogo(261) },
89
+ { id: 'm14', name: 'Troy', seed: 14, region: 'MIDWEST', logo: espnLogo(2653) },
90
+ { id: 'm15', name: 'Siena', seed: 15, region: 'MIDWEST', logo: espnLogo(2561) },
91
+ { id: 'm16', name: 'SFA', seed: 16, region: 'MIDWEST', logo: espnLogo(2617) },
92
+
93
+ // First Four teams (play-in games)
94
+ { id: 'ff1', name: 'Texas Southern', seed: 16, region: 'FIRST_FOUR', logo: espnLogo(2640) },
95
+ { id: 'ff2', name: 'Jackson State', seed: 16, region: 'FIRST_FOUR', logo: espnLogo(2296) },
96
+ { id: 'ff3', name: 'Wagner', seed: 16, region: 'FIRST_FOUR', logo: espnLogo(2718) },
97
+ { id: 'ff4', name: 'Bryant', seed: 16, region: 'FIRST_FOUR', logo: espnLogo(2803) },
98
+ ];
99
+
100
+ // Generate mock bracket with standard seeding matchups
101
+ function generateMockBracket(poolId) {
102
+ const games = [];
103
+ let gameIndex = 0;
104
+
105
+ // Helper to create game object
106
+ const createGame = (team1, team2, round, region) => ({
107
+ round,
108
+ espnGameId: `mock-${poolId}-${++gameIndex}`,
109
+ gameDate: new Date(Date.now() + (round * 2 + gameIndex) * 3600000), // Stagger games
110
+ team1Id: team1.id,
111
+ team1Name: team1.name,
112
+ team1Seed: team1.seed,
113
+ team1Logo: team1.logo || null,
114
+ team2Id: team2.id,
115
+ team2Name: team2.name,
116
+ team2Seed: team2.seed,
117
+ team2Logo: team2.logo || null,
118
+ region,
119
+ status: 'scheduled'
120
+ });
121
+
122
+ // Round 1: First Four (4 games)
123
+ const firstFourTeams = MOCK_TEAMS.filter(t => t.region === 'FIRST_FOUR');
124
+ for (let i = 0; i < firstFourTeams.length; i += 2) {
125
+ games.push(createGame(firstFourTeams[i], firstFourTeams[i + 1], 1, 'FIRST_FOUR'));
126
+ }
127
+
128
+ // Round 2: Round of 64 (32 games - standard 1v16, 2v15, etc.)
129
+ const regions = ['EAST', 'WEST', 'SOUTH', 'MIDWEST'];
130
+ for (const region of regions) {
131
+ const regionTeams = MOCK_TEAMS.filter(t => t.region === region);
132
+ // Standard bracket: 1v16, 8v9, 5v12, 4v13, 6v11, 3v14, 7v10, 2v15
133
+ const matchups = [
134
+ [1, 16], [8, 9], [5, 12], [4, 13], [6, 11], [3, 14], [7, 10], [2, 15]
135
+ ];
136
+ for (const [seed1, seed2] of matchups) {
137
+ const team1 = regionTeams.find(t => t.seed === seed1);
138
+ const team2 = regionTeams.find(t => t.seed === seed2);
139
+ if (team1 && team2) {
140
+ games.push(createGame(team1, team2, 2, region));
141
+ }
142
+ }
143
+ }
144
+
145
+ // Note: Rounds 3-7 will be generated dynamically based on Round 2 winners
146
+ // For simulation purposes, we'll generate placeholder games
147
+
148
+ return games;
149
+ }
150
+
151
+ class SurvivorSimulator {
152
+ /**
153
+ * Generate mock tournament games for a pool
154
+ */
155
+ async generateTournamentBracket(poolId) {
156
+ const games = generateMockBracket(poolId);
157
+ const imported = await survivorController.importTournamentGames(poolId, games);
158
+
159
+ console.log(`[Simulator] Generated ${imported.length} tournament games for pool ${poolId}`);
160
+ return imported;
161
+ }
162
+
163
+ /**
164
+ * Simulate a round with configurable upset chance
165
+ * @param {number} poolId - Pool ID
166
+ * @param {object} options - Simulation options
167
+ * @param {number} options.upsetChance - Probability of upset (0-1, default 0.2)
168
+ * @param {boolean} options.favorChalk - If true, favorites always win
169
+ */
170
+ async simulateRound(poolId, options = {}) {
171
+ const { upsetChance = 0.2, favorChalk = false } = options;
172
+
173
+ // Get pool info
174
+ const poolResult = await pool.query(
175
+ 'SELECT * FROM survivor_pools WHERE id = $1',
176
+ [poolId]
177
+ );
178
+
179
+ if (poolResult.rows.length === 0) {
180
+ throw new Error('Pool not found');
181
+ }
182
+
183
+ const survivorPool = poolResult.rows[0];
184
+ const currentRound = survivorPool.current_round;
185
+
186
+ // Get unresolved games for current round
187
+ const gamesResult = await pool.query(
188
+ `SELECT * FROM survivor_tournament_games
189
+ WHERE pool_id = $1 AND round = $2 AND status != 'final'`,
190
+ [poolId, currentRound]
191
+ );
192
+
193
+ const games = gamesResult.rows;
194
+ console.log(`[Simulator] Simulating ${games.length} games for round ${currentRound}`);
195
+
196
+ const results = [];
197
+
198
+ for (const game of games) {
199
+ // Determine winner based on seed and upset chance
200
+ const team1Seed = game.team1_seed || 8;
201
+ const team2Seed = game.team2_seed || 8;
202
+ const team1Favored = team1Seed < team2Seed;
203
+
204
+ let winnerId;
205
+ let winnerName;
206
+
207
+ if (favorChalk) {
208
+ // Chalk: favorite always wins
209
+ winnerId = team1Favored ? game.team1_id : game.team2_id;
210
+ winnerName = team1Favored ? game.team1_name : game.team2_name;
211
+ } else {
212
+ // Random with upset chance
213
+ const isUpset = Math.random() < upsetChance;
214
+ if (team1Favored) {
215
+ winnerId = isUpset ? game.team2_id : game.team1_id;
216
+ winnerName = isUpset ? game.team2_name : game.team1_name;
217
+ } else {
218
+ winnerId = isUpset ? game.team1_id : game.team2_id;
219
+ winnerName = isUpset ? game.team1_name : game.team2_name;
220
+ }
221
+ }
222
+
223
+ // Generate realistic scores
224
+ const baseScore = 65 + Math.floor(Math.random() * 25);
225
+ const margin = Math.floor(Math.random() * 20) + 3;
226
+ const winnerScore = baseScore + Math.floor(margin / 2);
227
+ const loserScore = baseScore - Math.floor(margin / 2);
228
+
229
+ const team1Score = winnerId === game.team1_id ? winnerScore : loserScore;
230
+ const team2Score = winnerId === game.team2_id ? winnerScore : loserScore;
231
+
232
+ // Update game result
233
+ await pool.query(
234
+ `UPDATE survivor_tournament_games
235
+ SET winner_id = $3, team1_score = $4, team2_score = $5, status = 'final', updated_at = NOW()
236
+ WHERE pool_id = $1 AND id = $2`,
237
+ [poolId, game.id, winnerId, team1Score, team2Score]
238
+ );
239
+
240
+ results.push({
241
+ gameId: game.id,
242
+ espnGameId: game.espn_game_id,
243
+ winner: winnerName,
244
+ team1Score,
245
+ team2Score,
246
+ isUpset: (team1Favored && winnerId === game.team2_id) || (!team1Favored && winnerId === game.team1_id)
247
+ });
248
+
249
+ console.log(` ${game.team1_name} (${team1Seed}) vs ${game.team2_name} (${team2Seed}): ${winnerName} wins ${team1Score}-${team2Score}`);
250
+ }
251
+
252
+ // Now resolve the round: process picks, eliminate losers, and advance to next round
253
+ console.log(`[Simulator] Resolving round ${currentRound}...`);
254
+ const survivorOracle = require('./survivorOracle');
255
+ const resolution = await survivorOracle.resolveCurrentRound(poolId);
256
+
257
+ return {
258
+ poolId,
259
+ round: currentRound,
260
+ gamesSimulated: results.length,
261
+ results,
262
+ resolution
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Generate test entries with random picks for current round
268
+ */
269
+ async generateTestEntries(poolId, count = 10) {
270
+ // Get pool
271
+ const poolResult = await pool.query(
272
+ 'SELECT * FROM survivor_pools WHERE id = $1',
273
+ [poolId]
274
+ );
275
+
276
+ if (poolResult.rows.length === 0) {
277
+ throw new Error('Pool not found');
278
+ }
279
+
280
+ const survivorPool = poolResult.rows[0];
281
+
282
+ // Get available teams for current round
283
+ const gamesResult = await pool.query(
284
+ `SELECT * FROM survivor_tournament_games
285
+ WHERE pool_id = $1 AND round = $2`,
286
+ [poolId, survivorPool.current_round]
287
+ );
288
+
289
+ const teams = [];
290
+ for (const game of gamesResult.rows) {
291
+ teams.push({
292
+ id: game.team1_id,
293
+ name: game.team1_name,
294
+ seed: game.team1_seed,
295
+ espnGameId: game.espn_game_id,
296
+ opponent: game.team2_name
297
+ });
298
+ teams.push({
299
+ id: game.team2_id,
300
+ name: game.team2_name,
301
+ seed: game.team2_seed,
302
+ espnGameId: game.espn_game_id,
303
+ opponent: game.team1_name
304
+ });
305
+ }
306
+
307
+ const entries = [];
308
+
309
+ for (let i = 0; i < count; i++) {
310
+ // Create test user if needed
311
+ const walletAddress = `TestWallet${Date.now()}${i}`;
312
+ const username = `TestUser${i + 1}`;
313
+
314
+ const userResult = await pool.query(
315
+ `INSERT INTO users (wallet_address, username)
316
+ VALUES ($1, $2)
317
+ ON CONFLICT (wallet_address) DO UPDATE SET username = EXCLUDED.username
318
+ RETURNING id`,
319
+ [walletAddress, username]
320
+ );
321
+ const userId = userResult.rows[0].id;
322
+
323
+ // Join pool
324
+ const entryResult = await pool.query(
325
+ `INSERT INTO survivor_entries (pool_id, user_id, wallet_address)
326
+ VALUES ($1, $2, $3)
327
+ ON CONFLICT (pool_id, user_id) DO NOTHING
328
+ RETURNING *`,
329
+ [poolId, userId, walletAddress]
330
+ );
331
+
332
+ if (entryResult.rows.length === 0) {
333
+ continue; // Already joined
334
+ }
335
+
336
+ const entry = entryResult.rows[0];
337
+
338
+ // Pick random team
339
+ const randomTeam = teams[Math.floor(Math.random() * teams.length)];
340
+
341
+ await pool.query(
342
+ `INSERT INTO survivor_picks
343
+ (entry_id, round, team_id, team_name, team_seed, espn_game_id, opponent_name)
344
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
345
+ [entry.id, survivorPool.current_round, randomTeam.id, randomTeam.name, randomTeam.seed, randomTeam.espnGameId, randomTeam.opponent]
346
+ );
347
+
348
+ entries.push({
349
+ ...entry,
350
+ username,
351
+ pick: randomTeam.name
352
+ });
353
+
354
+ console.log(`[Simulator] Created entry: ${username} picked ${randomTeam.name}`);
355
+ }
356
+
357
+ return {
358
+ poolId,
359
+ entriesCreated: entries.length,
360
+ entries
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Set round deadline to N minutes from now
366
+ */
367
+ async setDeadline(poolId, minutes) {
368
+ const deadline = new Date(Date.now() + minutes * 60 * 1000);
369
+
370
+ await pool.query(
371
+ `UPDATE survivor_pools SET round_deadline = $2, updated_at = NOW() WHERE id = $1`,
372
+ [poolId, deadline]
373
+ );
374
+
375
+ console.log(`[Simulator] Set deadline for pool ${poolId} to ${deadline.toISOString()}`);
376
+ return { poolId, deadline };
377
+ }
378
+
379
+ /**
380
+ * Reset pool to round 1 and clear all entries/picks
381
+ * @param {number} poolId - Pool ID
382
+ * @param {object} options - Reset options
383
+ * @param {boolean} options.deleteEntries - If true, also delete all entries (full reset)
384
+ */
385
+ async resetPool(poolId, options = {}) {
386
+ const { deleteEntries = false } = options;
387
+ const client = await pool.connect();
388
+
389
+ try {
390
+ await client.query('BEGIN');
391
+
392
+ // Delete all picks
393
+ await client.query(
394
+ `DELETE FROM survivor_picks WHERE entry_id IN (SELECT id FROM survivor_entries WHERE pool_id = $1)`,
395
+ [poolId]
396
+ );
397
+
398
+ if (deleteEntries) {
399
+ // Full reset: delete all entries
400
+ await client.query(
401
+ `DELETE FROM survivor_entries WHERE pool_id = $1`,
402
+ [poolId]
403
+ );
404
+ console.log(`[Simulator] Deleted all entries for pool ${poolId}`);
405
+ } else {
406
+ // Soft reset: just reset entries to alive
407
+ await client.query(
408
+ `UPDATE survivor_entries
409
+ SET is_alive = true, eliminated_at_round = NULL, updated_at = NOW()
410
+ WHERE pool_id = $1`,
411
+ [poolId]
412
+ );
413
+ }
414
+
415
+ // Delete tournament games (so bracket can be regenerated fresh)
416
+ await client.query(
417
+ `DELETE FROM survivor_tournament_games WHERE pool_id = $1`,
418
+ [poolId]
419
+ );
420
+
421
+ // Reset pool to round 1 and open status
422
+ await client.query(
423
+ `UPDATE survivor_pools
424
+ SET current_round = 1, round_deadline = NULL, status = 'open', updated_at = NOW()
425
+ WHERE id = $1`,
426
+ [poolId]
427
+ );
428
+
429
+ await client.query('COMMIT');
430
+
431
+ const message = deleteEntries
432
+ ? 'Pool fully reset (entries deleted)'
433
+ : 'Pool reset to round 1 (entries kept)';
434
+ console.log(`[Simulator] ${message}`);
435
+ return { poolId, message, entriesDeleted: deleteEntries };
436
+ } catch (error) {
437
+ await client.query('ROLLBACK');
438
+ throw error;
439
+ } finally {
440
+ client.release();
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Activate pool and set to round 2 (skip First Four)
446
+ */
447
+ async activatePool(poolId) {
448
+ await pool.query(
449
+ `UPDATE survivor_pools SET status = 'active', current_round = 2, updated_at = NOW() WHERE id = $1`,
450
+ [poolId]
451
+ );
452
+
453
+ console.log(`[Simulator] Pool ${poolId} activated at round 2`);
454
+ return { poolId, status: 'active', currentRound: 2 };
455
+ }
456
+
457
+ /**
458
+ * Delete test entries (cleanup)
459
+ */
460
+ async deleteTestEntries(poolId) {
461
+ const result = await pool.query(
462
+ `DELETE FROM survivor_entries
463
+ WHERE pool_id = $1 AND wallet_address LIKE 'TestWallet%'
464
+ RETURNING id`,
465
+ [poolId]
466
+ );
467
+
468
+ console.log(`[Simulator] Deleted ${result.rowCount} test entries from pool ${poolId}`);
469
+ return { poolId, deletedCount: result.rowCount };
470
+ }
471
+ }
472
+
473
+ module.exports = new SurvivorSimulator();
474
+ module.exports.SurvivorSimulator = SurvivorSimulator;
475
+ module.exports.MOCK_TEAMS = MOCK_TEAMS;