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,65 @@
1
+ // PM2 Ecosystem Configuration
2
+ // This ensures both API server and keeper bot run reliably
3
+
4
+ module.exports = {
5
+ apps: [
6
+ {
7
+ name: 'dubs-api',
8
+ script: './server.js',
9
+ instances: 1,
10
+ autorestart: true,
11
+ watch: false,
12
+ max_memory_restart: '500M',
13
+ env: {
14
+ NODE_ENV: 'development',
15
+ SOLANA_NETWORK: 'https://api.devnet.solana.com',
16
+ PORT: 3001,
17
+ },
18
+ env_production: {
19
+ NODE_ENV: 'production',
20
+ SOLANA_NETWORK: 'https://api.mainnet-beta.solana.com',
21
+ PORT: 3001,
22
+ },
23
+ error_file: './logs/api-error.log',
24
+ out_file: './logs/api-out.log',
25
+ log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
26
+ },
27
+ {
28
+ name: 'jackpot-keeper',
29
+ script: './scripts/jackpot/keeper.js',
30
+ instances: 1,
31
+ autorestart: true,
32
+ watch: false,
33
+ max_memory_restart: '300M',
34
+ env: {
35
+ NODE_ENV: 'development',
36
+ },
37
+ restart_delay: 5000, // Wait 5 seconds between restarts
38
+ max_restarts: 10, // Max 10 restarts in...
39
+ min_uptime: '10s', // ...10 seconds (prevents crash loop)
40
+ error_file: './logs/keeper-error.log',
41
+ out_file: './logs/keeper-out.log',
42
+ log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
43
+ },
44
+ {
45
+ name: 'sports-oracle',
46
+ script: './cron/oracleMonitor.js',
47
+ instances: 1,
48
+ autorestart: true,
49
+ watch: false,
50
+ max_memory_restart: '300M',
51
+ env: {
52
+ NODE_ENV: 'development',
53
+ ORACLE_CHECK_INTERVAL: '60000', // Check every 60 seconds
54
+ NOTIFY_BEFORE_MINUTES: '10', // Notify 10 minutes before game starts
55
+ },
56
+ restart_delay: 5000,
57
+ max_restarts: 10,
58
+ min_uptime: '10s',
59
+ error_file: './logs/oracle-error.log',
60
+ out_file: './logs/oracle-out.log',
61
+ log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
62
+ },
63
+ ],
64
+ };
65
+
package/env.template ADDED
@@ -0,0 +1,125 @@
1
+ # Dubs Server Environment Variables
2
+ # Copy this to .env and update values
3
+
4
+ # Solana Configuration
5
+ SOLANA_NETWORK=http://127.0.0.1:8899
6
+ # For devnet: https://api.devnet.solana.com
7
+ # For mainnet: https://api.mainnet-beta.solana.com
8
+
9
+ PROGRAM_ID=8DJTkgk6MDr6tPtw4v2VzYAz9WWvmCg6786vZrEK3o5q
10
+
11
+ # Oracle Configuration (for Automatic Sports Mode)
12
+ ORACLE_WALLET_PATH=./wallets/oracle.json
13
+ ORACLE_CHECK_INTERVAL=60000
14
+ # Check interval in milliseconds (60000 = 1 minute, 300000 = 5 minutes)
15
+
16
+ NOTIFY_BEFORE_MINUTES=10
17
+ # Send "game starting soon" notification X minutes before lock time (default: 10)
18
+
19
+ # Telegram Bot Configuration (for notifications)
20
+ TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
21
+ # Get this from @BotFather on Telegram
22
+
23
+ TELEGRAM_BOT_URL=https://your-bot-url.herokuapp.com
24
+ # URL to your deployed Telegram bot (for sending private chat notifications)
25
+ # Example: https://dubs-telegram-bot-xxxxx.herokuapp.com
26
+
27
+ # API URLs
28
+ LIVE_SCORES_API_URL=http://localhost:3000
29
+ # Your dubs-api endpoint with /api/livescores/:league
30
+
31
+ DUBS_GAMES_API_URL=http://localhost:3001
32
+ # Your dubs-games-api endpoint (Firebase database)
33
+
34
+ DUBS_SERVER_URL=http://localhost:3001
35
+ # Your dubs-server endpoint (PostgreSQL database)
36
+ # For production (Heroku): https://dubs-server-production.herokuapp.com
37
+ # For local dev: http://localhost:3001 (should match PORT above)
38
+
39
+ # Server Configuration
40
+ PORT=3001
41
+ NODE_ENV=development
42
+
43
+ # Database Configuration (PostgreSQL)
44
+ DATABASE_URL=postgresql://username:password@localhost:5432/dubs_db
45
+ # For production (Heroku adds this automatically)
46
+ # For local dev: postgresql://postgres:password@localhost:5432/dubs_db
47
+
48
+ # Redis Configuration (for high-performance caching)
49
+ REDIS_URL=redis://localhost:6379
50
+ # For Heroku: Add Heroku Redis addon (automatically sets REDIS_URL)
51
+ # For local dev: redis://localhost:6379
52
+ # Optional: If not set, server runs in PostgreSQL-only mode (slightly slower)
53
+
54
+ # Authentication & Security
55
+ JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
56
+ # CRITICAL: Generate a secure random string for production!
57
+ # Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
58
+
59
+ JWT_EXPIRES_IN=7d
60
+ # Token expiration time (e.g., 7d, 24h, 30m)
61
+
62
+ # AWS S3 Configuration (for avatar uploads)
63
+ AWS_REGION=us-east-1
64
+ AWS_ACCESS_KEY_ID=your_aws_access_key
65
+ AWS_SECRET_ACCESS_KEY=your_aws_secret_key
66
+ AWS_S3_BUCKET_NAME=dubs-avatars
67
+
68
+ # Exchange Rate API Configuration
69
+ EXCHANGE_API_KEY=757419e9be20039acaf308a9
70
+ # API key for exchangerate-api.com (v6)
71
+ # Sign up at: https://www.exchangerate-api.com
72
+
73
+ EXCHANGE_API_BASE_URL=https://v6.exchangerate-api.com/v6
74
+ # Base URL for exchange rate API
75
+
76
+ EXCHANGE_CACHE_TTL=300
77
+ # Cache time-to-live in seconds (300 = 5 minutes)
78
+
79
+ BASE_CURRENCY=USD
80
+ # Default base currency for exchange rates
81
+
82
+ SUPPORTED_CURRENCIES=USD,EUR,CAD,GBP,JPY,AUD,CHF,CNY,SEK,NZD
83
+ # Comma-separated list of supported currencies
84
+
85
+ # PandaScore API Configuration (Esports)
86
+ PANDASCORE_API_KEY=your_pandascore_api_key_here
87
+ # Get a free API key at: https://pandascore.co
88
+
89
+ # Crypto Price API Configuration (CoinGecko)
90
+ CRYPTO_PRICE_CACHE_TTL=300
91
+ # Cache time-to-live in seconds (300 = 5 minutes, matches exchange rates)
92
+
93
+ # ============================================
94
+ # 🎰 JACKPOT CONFIGURATION
95
+ # ============================================
96
+
97
+ # Jackpot Program ID (Solana on-chain program)
98
+ JACKPOT_PROGRAM_ID=BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz
99
+ # Devnet: BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz
100
+ # Mainnet: <DEPLOY AND UPDATE>
101
+
102
+ # Jackpot Oracle Wallet (public key)
103
+ JACKPOT_ORACLE_WALLET=FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa
104
+ # This wallet submits randomness for provably fair rounds
105
+
106
+ # Keeper Private Key (JSON array format)
107
+ # CRITICAL: Get from oracle.json wallet file
108
+ # Format: [1,2,3,...,64] (64 byte array as JSON)
109
+ KEEPER_PRIVATE_KEY=
110
+ # To export: cat wallets/oracle.json
111
+
112
+ # Keeper API Base URL
113
+ API_BASE_URL=http://localhost:3001
114
+ # For Heroku devnet: https://dubs-server-dev-xxxxx.herokuapp.com
115
+ # For Heroku mainnet: https://dubs-server-prod-xxxxx.herokuapp.com
116
+
117
+ # ============================================
118
+ # 🎨 MATCHUP IMAGE GENERATION
119
+ # ============================================
120
+
121
+ # Base URL for fetching team logos (for matchup image generation)
122
+ MATCHUP_LOGO_BASE_URL=http://localhost:3000
123
+ # For local dev: http://localhost:3000 (your SPA's public folder)
124
+ # For production: https://dubs.app (or wherever your SPA is hosted)
125
+
@@ -0,0 +1,136 @@
1
+ /**
2
+ * 🔑 API Key Authentication Middleware
3
+ *
4
+ * Validates developer API keys from the X-API-Key header.
5
+ * Hashes the key and looks up in developer_api_keys table.
6
+ * Attaches developer app info to req.developerApp on success.
7
+ */
8
+
9
+ const crypto = require('crypto');
10
+ const { pool } = require('../services/db');
11
+
12
+ /**
13
+ * Hash an API key for lookup (same SHA-256 used at key creation)
14
+ */
15
+ function hashApiKey(key) {
16
+ return crypto.createHash('sha256').update(key).digest('hex');
17
+ }
18
+
19
+ /**
20
+ * Middleware: Require valid API key in X-API-Key header
21
+ * Attaches req.developerApp with { appId, developerId, commissionWallet, environment, appName }
22
+ */
23
+ async function apiKeyAuth(req, res, next) {
24
+ const apiKey = req.headers['x-api-key'];
25
+
26
+ if (!apiKey) {
27
+ return res.status(401).json({
28
+ success: false,
29
+ error: 'Missing API key — set X-API-Key header',
30
+ code: 'NO_API_KEY'
31
+ });
32
+ }
33
+
34
+ // Validate prefix format
35
+ if (!apiKey.startsWith('dubs_test_') && !apiKey.startsWith('dubs_live_')) {
36
+ return res.status(401).json({
37
+ success: false,
38
+ error: 'Invalid API key format',
39
+ code: 'INVALID_KEY_FORMAT'
40
+ });
41
+ }
42
+
43
+ try {
44
+ const keyHash = hashApiKey(apiKey);
45
+
46
+ const result = await pool.query(`
47
+ SELECT
48
+ k.id as key_id,
49
+ k.app_id,
50
+ k.environment,
51
+ k.is_active,
52
+ a.developer_id,
53
+ a.app_name,
54
+ a.status as app_status,
55
+ a.network_mode,
56
+ d.commission_wallet,
57
+ d.wallet_address as developer_wallet
58
+ FROM developer_api_keys k
59
+ JOIN developer_apps a ON k.app_id = a.id
60
+ JOIN developer_accounts d ON a.developer_id = d.id
61
+ WHERE k.key_hash = $1
62
+ `, [keyHash]);
63
+
64
+ if (result.rows.length === 0) {
65
+ return res.status(401).json({
66
+ success: false,
67
+ error: 'Invalid API key',
68
+ code: 'INVALID_API_KEY'
69
+ });
70
+ }
71
+
72
+ const row = result.rows[0];
73
+
74
+ if (!row.is_active) {
75
+ return res.status(401).json({
76
+ success: false,
77
+ error: 'API key has been revoked',
78
+ code: 'KEY_REVOKED'
79
+ });
80
+ }
81
+
82
+ if (row.app_status !== 'active') {
83
+ return res.status(403).json({
84
+ success: false,
85
+ error: `App is ${row.app_status}`,
86
+ code: 'APP_INACTIVE'
87
+ });
88
+ }
89
+
90
+ // Attach developer context to request
91
+ req.developerApp = {
92
+ keyId: row.key_id,
93
+ appId: row.app_id,
94
+ developerId: row.developer_id,
95
+ commissionWallet: row.commission_wallet,
96
+ developerWallet: row.developer_wallet,
97
+ environment: row.environment,
98
+ appName: row.app_name,
99
+ networkMode: row.network_mode,
100
+ };
101
+
102
+ // Update last_used_at (fire-and-forget, don't block the request)
103
+ pool.query(
104
+ 'UPDATE developer_api_keys SET last_used_at = NOW() WHERE id = $1',
105
+ [row.key_id]
106
+ ).catch(() => {});
107
+
108
+ next();
109
+ } catch (error) {
110
+ console.error('[ApiKeyAuth] Error:', error.message);
111
+ return res.status(500).json({
112
+ success: false,
113
+ error: 'Authentication error',
114
+ code: 'AUTH_ERROR'
115
+ });
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Log an API call for analytics (fire-and-forget)
121
+ */
122
+ function logApiCall(req, statusCode, responseTimeMs) {
123
+ if (!req.developerApp) return;
124
+
125
+ pool.query(
126
+ `INSERT INTO developer_api_logs (app_id, endpoint, method, status_code, response_time_ms)
127
+ VALUES ($1, $2, $3, $4, $5)`,
128
+ [req.developerApp.appId, req.originalUrl, req.method, statusCode, responseTimeMs]
129
+ ).catch(() => {});
130
+ }
131
+
132
+ module.exports = {
133
+ apiKeyAuth,
134
+ hashApiKey,
135
+ logApiCall,
136
+ };
@@ -0,0 +1,214 @@
1
+ /**
2
+ * 🔐 JWT Authentication Middleware
3
+ *
4
+ * Validates JWT tokens from cookies and attaches user info to requests
5
+ */
6
+
7
+ const jwt = require('jsonwebtoken');
8
+ const { pool } = require('../services/db'); // Shared database pool
9
+
10
+ require('dotenv').config();
11
+
12
+ // JWT Secret - MUST be set in environment variables
13
+ const JWT_SECRET = process.env.JWT_SECRET || (() => {
14
+ console.warn('⚠️ WARNING: JWT_SECRET not set! Using default (INSECURE for production)');
15
+ return 'dev-secret-change-in-production';
16
+ })();
17
+
18
+ const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
19
+
20
+ /**
21
+ * Generate JWT token for a user
22
+ */
23
+ function generateToken(walletAddress, userId) {
24
+ return jwt.sign(
25
+ {
26
+ walletAddress,
27
+ userId,
28
+ iat: Math.floor(Date.now() / 1000)
29
+ },
30
+ JWT_SECRET,
31
+ { expiresIn: JWT_EXPIRES_IN }
32
+ );
33
+ }
34
+
35
+ /**
36
+ * Middleware: Authenticate user via JWT token (Authorization header)
37
+ * Attaches user info to req.user if valid
38
+ */
39
+ async function authenticate(req, res, next) {
40
+ try {
41
+ // Get token from Authorization header
42
+ const authHeader = req.headers.authorization;
43
+
44
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
45
+ return res.status(401).json({
46
+ success: false,
47
+ error: 'Authentication required - Missing or invalid Authorization header',
48
+ code: 'NO_TOKEN'
49
+ });
50
+ }
51
+
52
+ const token = authHeader.substring(7); // Remove 'Bearer ' prefix
53
+
54
+ // Verify token
55
+ let decoded;
56
+ try {
57
+ decoded = jwt.verify(token, JWT_SECRET);
58
+ } catch (err) {
59
+ if (err.name === 'TokenExpiredError') {
60
+ return res.status(401).json({
61
+ success: false,
62
+ error: 'Session expired',
63
+ code: 'TOKEN_EXPIRED'
64
+ });
65
+ }
66
+ return res.status(401).json({
67
+ success: false,
68
+ error: 'Invalid token',
69
+ code: 'INVALID_TOKEN'
70
+ });
71
+ }
72
+
73
+ // Check if session exists in database (optional but recommended)
74
+ const sessionCheck = await pool.query(
75
+ 'SELECT * FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
76
+ [decoded.walletAddress, hashToken(token)]
77
+ );
78
+
79
+ if (sessionCheck.rows.length === 0) {
80
+ return res.status(401).json({
81
+ success: false,
82
+ error: 'Session not found or expired',
83
+ code: 'SESSION_INVALID'
84
+ });
85
+ }
86
+
87
+ // Optional: Verify wallet address from header matches token
88
+ const walletHeader = req.headers['x-wallet-address'];
89
+ if (walletHeader && walletHeader !== decoded.walletAddress) {
90
+ return res.status(403).json({
91
+ success: false,
92
+ error: 'Wallet address mismatch',
93
+ code: 'WALLET_MISMATCH'
94
+ });
95
+ }
96
+
97
+ // Attach user info to request
98
+ req.user = {
99
+ walletAddress: decoded.walletAddress,
100
+ userId: decoded.userId,
101
+ };
102
+
103
+ // Update last activity
104
+ await pool.query(
105
+ 'UPDATE user_sessions SET last_activity = NOW() WHERE wallet_address = $1 AND token_hash = $2',
106
+ [decoded.walletAddress, hashToken(token)]
107
+ );
108
+
109
+ next();
110
+ } catch (error) {
111
+ console.error('[Auth] Middleware error:', error);
112
+ return res.status(500).json({
113
+ success: false,
114
+ error: 'Authentication error',
115
+ code: 'AUTH_ERROR'
116
+ });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Optional middleware: Only check if authenticated but don't require it
122
+ * Useful for endpoints that behave differently for logged-in users
123
+ */
124
+ async function optionalAuth(req, res, next) {
125
+ try {
126
+ const authHeader = req.headers.authorization;
127
+
128
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
129
+ req.user = null;
130
+ return next();
131
+ }
132
+
133
+ const token = authHeader.substring(7);
134
+ const decoded = jwt.verify(token, JWT_SECRET);
135
+ req.user = {
136
+ walletAddress: decoded.walletAddress,
137
+ userId: decoded.userId,
138
+ };
139
+
140
+ next();
141
+ } catch (error) {
142
+ // If token is invalid, just continue without user
143
+ req.user = null;
144
+ next();
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Hash token for storage (don't store raw tokens in database)
150
+ */
151
+ function hashToken(token) {
152
+ const crypto = require('crypto');
153
+ return crypto.createHash('sha256').update(token).digest('hex');
154
+ }
155
+
156
+ /**
157
+ * Store session in database
158
+ */
159
+ async function createSession(walletAddress, userId, token, expiresAt) {
160
+ const tokenHash = hashToken(token);
161
+
162
+ await pool.query(
163
+ `INSERT INTO user_sessions (wallet_address, user_id, token_hash, expires_at, created_at, last_activity)
164
+ VALUES ($1, $2, $3, $4, NOW(), NOW())`,
165
+ [walletAddress, userId, tokenHash, expiresAt]
166
+ );
167
+ }
168
+
169
+ /**
170
+ * Delete session from database
171
+ */
172
+ async function deleteSession(walletAddress, token) {
173
+ const tokenHash = hashToken(token);
174
+
175
+ await pool.query(
176
+ 'DELETE FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2',
177
+ [walletAddress, tokenHash]
178
+ );
179
+ }
180
+
181
+ /**
182
+ * Delete all sessions for a wallet (logout from all devices)
183
+ */
184
+ async function deleteAllSessions(walletAddress) {
185
+ await pool.query(
186
+ 'DELETE FROM user_sessions WHERE wallet_address = $1',
187
+ [walletAddress]
188
+ );
189
+ }
190
+
191
+ /**
192
+ * Clean up expired sessions (call this periodically)
193
+ */
194
+ async function cleanupExpiredSessions() {
195
+ const result = await pool.query(
196
+ 'DELETE FROM user_sessions WHERE expires_at < NOW()'
197
+ );
198
+ return result.rowCount;
199
+ }
200
+
201
+ module.exports = {
202
+ authenticate,
203
+ optionalAuth,
204
+ generateToken,
205
+ createSession,
206
+ deleteSession,
207
+ deleteAllSessions,
208
+ cleanupExpiredSessions,
209
+ hashToken,
210
+ JWT_SECRET,
211
+ JWT_EXPIRES_IN
212
+ };
213
+
214
+
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Developer User Authentication Middleware
3
+ *
4
+ * Validates Authorization: Bearer <JWT> after apiKeyAuth has already run.
5
+ * Reuses the same JWT_SECRET, hashToken, and user_sessions table as the main auth system.
6
+ * Attaches req.developerUser = { walletAddress, userId } (NOT req.user to avoid collision).
7
+ */
8
+
9
+ const jwt = require('jsonwebtoken');
10
+ const { pool } = require('../services/db');
11
+ const { JWT_SECRET, hashToken } = require('./authenticate');
12
+
13
+ async function developerUserAuth(req, res, next) {
14
+ try {
15
+ const authHeader = req.headers.authorization;
16
+
17
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
18
+ return res.status(401).json({
19
+ success: false,
20
+ error: { code: 'no_user_token', message: 'Authorization header with Bearer token is required' },
21
+ });
22
+ }
23
+
24
+ const token = authHeader.substring(7);
25
+
26
+ let decoded;
27
+ try {
28
+ decoded = jwt.verify(token, JWT_SECRET);
29
+ } catch (err) {
30
+ if (err.name === 'TokenExpiredError') {
31
+ return res.status(401).json({
32
+ success: false,
33
+ error: { code: 'token_expired', message: 'User token has expired' },
34
+ });
35
+ }
36
+ return res.status(401).json({
37
+ success: false,
38
+ error: { code: 'invalid_token', message: 'Invalid user token' },
39
+ });
40
+ }
41
+
42
+ // Check session exists in user_sessions (same table as main auth)
43
+ const sessionCheck = await pool.query(
44
+ 'SELECT 1 FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
45
+ [decoded.walletAddress, hashToken(token)]
46
+ );
47
+
48
+ if (sessionCheck.rows.length === 0) {
49
+ return res.status(401).json({
50
+ success: false,
51
+ error: { code: 'session_invalid', message: 'Session not found or expired' },
52
+ });
53
+ }
54
+
55
+ req.developerUser = {
56
+ walletAddress: decoded.walletAddress,
57
+ userId: decoded.userId,
58
+ };
59
+
60
+ // Update last activity (fire-and-forget)
61
+ pool.query(
62
+ 'UPDATE user_sessions SET last_activity = NOW() WHERE wallet_address = $1 AND token_hash = $2',
63
+ [decoded.walletAddress, hashToken(token)]
64
+ ).catch(() => {});
65
+
66
+ next();
67
+ } catch (error) {
68
+ console.error('[DevUserAuth] Middleware error:', error);
69
+ return res.status(500).json({
70
+ success: false,
71
+ error: { code: 'auth_error', message: 'Authentication error' },
72
+ });
73
+ }
74
+ }
75
+
76
+ module.exports = { developerUserAuth };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 🔐 WebSocket JWT Authentication Middleware
3
+ *
4
+ * Authenticates socket.io connections using JWT tokens from auth parameter
5
+ */
6
+
7
+ const jwt = require('jsonwebtoken');
8
+ const { pool } = require('../services/db'); // Shared database pool
9
+
10
+ require('dotenv').config();
11
+
12
+ const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production';
13
+
14
+ /**
15
+ * Socket.io middleware to authenticate connections
16
+ */
17
+ function socketAuthMiddleware(socket, next) {
18
+ try {
19
+ // Get token from auth parameter (sent by client during connection)
20
+ const token = socket.handshake.auth?.token;
21
+
22
+ if (!token) {
23
+ console.log('[Socket] Connection rejected: No token in auth parameter');
24
+ return next(new Error('Authentication required - Missing token'));
25
+ }
26
+
27
+ // Verify JWT
28
+ let decoded;
29
+ try {
30
+ decoded = jwt.verify(token, JWT_SECRET);
31
+ } catch (err) {
32
+ console.log('[Socket] Connection rejected: Invalid token');
33
+ return next(new Error('Invalid token'));
34
+ }
35
+
36
+ // Verify session exists in database
37
+ const crypto = require('crypto');
38
+ const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
39
+
40
+ pool.query(
41
+ 'SELECT * FROM user_sessions WHERE wallet_address = $1 AND token_hash = $2 AND expires_at > NOW()',
42
+ [decoded.walletAddress, tokenHash]
43
+ ).then(result => {
44
+ if (result.rows.length === 0) {
45
+ console.log('[Socket] Connection rejected: Session not found');
46
+ return next(new Error('Session expired'));
47
+ }
48
+
49
+ // Attach user info to socket
50
+ socket.user = {
51
+ userId: decoded.userId,
52
+ walletAddress: decoded.walletAddress,
53
+ };
54
+
55
+ console.log(`[Socket] ✅ Authenticated: ${decoded.walletAddress.slice(0, 8)}...`);
56
+ next();
57
+ }).catch(err => {
58
+ console.error('[Socket] Database error:', err);
59
+ next(new Error('Authentication error'));
60
+ });
61
+ } catch (error) {
62
+ console.error('[Socket] Auth middleware error:', error);
63
+ next(new Error('Authentication error'));
64
+ }
65
+ }
66
+
67
+ module.exports = { socketAuthMiddleware };
68
+
69
+