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,130 @@
1
+ /**
2
+ * šŸ” Check Current Chat Database Schema
3
+ *
4
+ * Inspects the actual Heroku Postgres database to see what we have
5
+ */
6
+
7
+ const { Pool } = require('pg');
8
+
9
+ async function checkSchema() {
10
+ const pool = new Pool({
11
+ connectionString: process.env.DATABASE_URL,
12
+ ssl: process.env.DATABASE_URL ? { rejectUnauthorized: false } : false,
13
+ });
14
+
15
+ try {
16
+ console.log('šŸ” Checking Heroku Postgres database...\n');
17
+
18
+ // Check if table exists
19
+ const tableCheck = await pool.query(`
20
+ SELECT EXISTS (
21
+ SELECT FROM information_schema.tables
22
+ WHERE table_name = 'chat_messages'
23
+ );
24
+ `);
25
+
26
+ if (!tableCheck.rows[0].exists) {
27
+ console.log('āŒ Table chat_messages does NOT exist!');
28
+ await pool.end();
29
+ return;
30
+ }
31
+
32
+ console.log('āœ… Table chat_messages EXISTS\n');
33
+
34
+ // Get all columns
35
+ const columns = await pool.query(`
36
+ SELECT
37
+ column_name,
38
+ data_type,
39
+ is_nullable,
40
+ column_default
41
+ FROM information_schema.columns
42
+ WHERE table_name = 'chat_messages'
43
+ ORDER BY ordinal_position;
44
+ `);
45
+
46
+ console.log('šŸ“Š CURRENT TABLE STRUCTURE:');
47
+ console.log('─'.repeat(80));
48
+ columns.rows.forEach(row => {
49
+ console.log(` ${row.column_name.padEnd(30)} ${row.data_type.padEnd(20)} ${row.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'}`);
50
+ if (row.column_default) {
51
+ console.log(` Default: ${row.column_default}`);
52
+ }
53
+ });
54
+ console.log('─'.repeat(80));
55
+
56
+ // Get indexes
57
+ const indexes = await pool.query(`
58
+ SELECT indexname, indexdef
59
+ FROM pg_indexes
60
+ WHERE tablename = 'chat_messages';
61
+ `);
62
+
63
+ if (indexes.rows.length > 0) {
64
+ console.log('\nšŸ”‘ INDEXES:');
65
+ indexes.rows.forEach(row => {
66
+ console.log(` ${row.indexname}`);
67
+ console.log(` ${row.indexdef}`);
68
+ });
69
+ }
70
+
71
+ // Get row count
72
+ const count = await pool.query(`SELECT COUNT(*) FROM chat_messages;`);
73
+ console.log(`\nšŸ’¬ Total messages in database: ${count.rows[0].count}`);
74
+
75
+ // Get sample messages
76
+ const sample = await pool.query(`
77
+ SELECT * FROM chat_messages
78
+ ORDER BY timestamp DESC
79
+ LIMIT 3;
80
+ `);
81
+
82
+ if (sample.rows.length > 0) {
83
+ console.log('\nšŸ“ SAMPLE MESSAGES (most recent):');
84
+ sample.rows.forEach((msg, i) => {
85
+ console.log(`\n Message ${i + 1}:`);
86
+ console.log(` ID: ${msg.id}`);
87
+ console.log(` Wallet: ${msg.wallet_address?.slice(0, 8)}...`);
88
+ console.log(` Message: ${msg.message?.slice(0, 50)}...`);
89
+ console.log(` Time: ${msg.timestamp}`);
90
+ // Check for new columns
91
+ if ('is_winner_announcement' in msg) {
92
+ console.log(` šŸ† Winner: ${msg.is_winner_announcement}`);
93
+ }
94
+ if ('win_amount' in msg) {
95
+ console.log(` šŸ’° Amount: ${msg.win_amount}`);
96
+ }
97
+ if ('round_id' in msg) {
98
+ console.log(` šŸŽ° Round: ${msg.round_id}`);
99
+ }
100
+ });
101
+ }
102
+
103
+ // Check what columns are MISSING for winner announcements
104
+ console.log('\nšŸ”„ WINNER ANNOUNCEMENT FEATURE CHECK:');
105
+ const hasWinnerCol = columns.rows.some(c => c.column_name === 'is_winner_announcement');
106
+ const hasAmountCol = columns.rows.some(c => c.column_name === 'win_amount');
107
+ const hasRoundCol = columns.rows.some(c => c.column_name === 'round_id');
108
+
109
+ console.log(` is_winner_announcement: ${hasWinnerCol ? 'āœ… EXISTS' : 'āŒ MISSING'}`);
110
+ console.log(` win_amount: ${hasAmountCol ? 'āœ… EXISTS' : 'āŒ MISSING'}`);
111
+ console.log(` round_id: ${hasRoundCol ? 'āœ… EXISTS' : 'āŒ MISSING'}`);
112
+
113
+ if (!hasWinnerCol || !hasAmountCol || !hasRoundCol) {
114
+ console.log('\nāš ļø MIGRATION NEEDED! Run: node scripts/migrate-chat-winners.js');
115
+ } else {
116
+ console.log('\nāœ… All columns exist! Winner announcements should work!');
117
+ }
118
+
119
+ await pool.end();
120
+ } catch (error) {
121
+ console.error('āŒ Error:', error.message);
122
+ console.error(error);
123
+ await pool.end();
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ // Run it
129
+ checkSchema();
130
+
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+
3
+ # šŸ” Check Chat Database Schema on Heroku
4
+ # This runs the check script on Heroku directly
5
+
6
+ echo "šŸš€ Checking database schema on Heroku..."
7
+ echo ""
8
+
9
+ # Run the check script on Heroku
10
+ heroku run node scripts/check-chat-schema.js --app dubs-server
11
+
12
+ echo ""
13
+ echo "āœ… Check complete!"
14
+
@@ -0,0 +1,54 @@
1
+ const { Connection, PublicKey } = require('@solana/web3.js');
2
+ const crypto = require('crypto');
3
+
4
+ const conn = new Connection('https://api.mainnet-beta.solana.com');
5
+ const PROGRAM_ID = new PublicKey('85wJGp9uc8w2FeKX9CEHsudTo1UVCrmuRFy37oCcaoG1');
6
+
7
+ function gameIdToU64(gameId) {
8
+ const hash = crypto.createHash('sha256').update(gameId).digest();
9
+ return hash.readBigUInt64LE(0);
10
+ }
11
+
12
+ function getGamePDA(gameId) {
13
+ const gameIdNum = gameIdToU64(gameId);
14
+ const gameIdBuf = Buffer.alloc(8);
15
+ gameIdBuf.writeBigUInt64LE(gameIdNum);
16
+ const [pda] = PublicKey.findProgramAddressSync([Buffer.from('game'), gameIdBuf], PROGRAM_ID);
17
+ return pda;
18
+ }
19
+
20
+ async function checkOracleInGame(gameId) {
21
+ const pda = getGamePDA(gameId);
22
+ console.log(`Game: ${gameId}`);
23
+ console.log(`PDA: ${pda.toString()}`);
24
+
25
+ const account = await conn.getAccountInfo(pda);
26
+ if (!account) {
27
+ console.log('āŒ Game account not found (might not be created yet)');
28
+ return;
29
+ }
30
+
31
+ const data = account.data;
32
+ // Oracle field is at a specific offset in the Game struct
33
+ // After many fields, the oracle Pubkey is 32 bytes
34
+
35
+ // Rough parsing - oracle is near the end before claimed_players
36
+ // Let's look for both possible oracle addresses
37
+ const dataHex = data.toString('hex');
38
+
39
+ const OLD_ORACLE = 'b93a8976ea13d935dcaf96f0cb26ab727f51191d63c016724230c2c1c8bd215e'; // DU4CHEH...
40
+ const NEW_ORACLE = 'd790532829777790e1e36b4bcbd2516f45c480c6787a24562023caae1e0ee268bd23'; // FWUJCth...
41
+
42
+ if (dataHex.includes(OLD_ORACLE)) {
43
+ console.log('āŒ OLD ORACLE DETECTED: DU4CHEHUJ2EeezAXyfyi8vLB7dmXKq41myKnA5DZ9Mru');
44
+ console.log(' This game will have resolution issues!');
45
+ } else if (dataHex.includes(NEW_ORACLE.slice(0, 64))) {
46
+ console.log('āœ… NEW ORACLE DETECTED: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa');
47
+ console.log(' This game will resolve automatically! āœ…');
48
+ } else {
49
+ console.log('šŸ” Oracle address not clearly identified in data');
50
+ console.log(' Data length:', data.length, 'bytes');
51
+ }
52
+ }
53
+
54
+ checkOracleInGame('sport-1767213423275-k0s504tfl').catch(console.error);
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Database Cleanup Script
5
+ *
6
+ * Manages data retention to prevent unbounded growth:
7
+ * - Keeps essential audit data
8
+ * - Archives old logs
9
+ * - Maintains performance
10
+ *
11
+ * Run via Heroku Scheduler: Daily at 3am
12
+ */
13
+
14
+ const { Pool } = require('pg');
15
+
16
+ const RETENTION_POLICIES = {
17
+ // Keep completed round records forever (users need to verify)
18
+ keeper_rounds_resolved: null, // Never delete
19
+
20
+ // Archive old incomplete/stuck rounds after 30 days
21
+ keeper_rounds_incomplete: 30, // days
22
+
23
+ // Keep detailed action logs for 30 days
24
+ keeper_actions: 30, // days
25
+
26
+ // Keep health snapshots for 90 days
27
+ keeper_health: 90, // days
28
+
29
+ // Keep jackpot history forever (verification data)
30
+ jackpot_rounds: null, // Never delete
31
+ };
32
+
33
+ class DatabaseCleaner {
34
+ constructor() {
35
+ this.pool = new Pool({
36
+ connectionString: process.env.DATABASE_URL,
37
+ ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
38
+ });
39
+ }
40
+
41
+ async getTableStats() {
42
+ const stats = {};
43
+
44
+ const tables = ['keeper_rounds', 'keeper_actions', 'keeper_health', 'jackpot_rounds'];
45
+
46
+ for (const table of tables) {
47
+ const result = await this.pool.query(`SELECT COUNT(*) as count FROM ${table}`);
48
+ const sizeResult = await this.pool.query(`
49
+ SELECT pg_size_pretty(pg_total_relation_size('${table}')) as size
50
+ `);
51
+ stats[table] = {
52
+ rows: parseInt(result.rows[0].count),
53
+ size: sizeResult.rows[0].size
54
+ };
55
+ }
56
+
57
+ return stats;
58
+ }
59
+
60
+ async cleanupKeeperActions() {
61
+ console.log('\n🧹 Cleaning keeper_actions...');
62
+
63
+ const days = RETENTION_POLICIES.keeper_actions;
64
+
65
+ const result = await this.pool.query(`
66
+ DELETE FROM keeper_actions
67
+ WHERE timestamp < NOW() - INTERVAL '${days} days'
68
+ RETURNING id
69
+ `);
70
+
71
+ console.log(` Deleted ${result.rowCount} old action logs (older than ${days} days)`);
72
+ return result.rowCount;
73
+ }
74
+
75
+ async cleanupKeeperHealth() {
76
+ console.log('\n🧹 Cleaning keeper_health...');
77
+
78
+ const days = RETENTION_POLICIES.keeper_health;
79
+
80
+ const result = await this.pool.query(`
81
+ DELETE FROM keeper_health
82
+ WHERE timestamp < NOW() - INTERVAL '${days} days'
83
+ RETURNING id
84
+ `);
85
+
86
+ console.log(` Deleted ${result.rowCount} old health snapshots (older than ${days} days)`);
87
+ return result.rowCount;
88
+ }
89
+
90
+ async archiveOldIncompleteRounds() {
91
+ console.log('\n🧹 Archiving incomplete keeper_rounds...');
92
+
93
+ const days = RETENTION_POLICIES.keeper_rounds_incomplete;
94
+
95
+ // Only delete rounds that are stuck/incomplete and old
96
+ const result = await this.pool.query(`
97
+ DELETE FROM keeper_rounds
98
+ WHERE status NOT IN ('resolved', 'open')
99
+ AND updated_at < NOW() - INTERVAL '${days} days'
100
+ RETURNING round_id, status
101
+ `);
102
+
103
+ console.log(` Archived ${result.rowCount} old incomplete rounds (older than ${days} days)`);
104
+
105
+ if (result.rowCount > 0) {
106
+ console.log(' Rounds archived:', result.rows.map(r => `${r.round_id} (${r.status})`).join(', '));
107
+ }
108
+
109
+ return result.rowCount;
110
+ }
111
+
112
+ async vacuum() {
113
+ console.log('\nšŸ—œļø Running VACUUM to reclaim space...');
114
+
115
+ await this.pool.query('VACUUM ANALYZE keeper_actions');
116
+ await this.pool.query('VACUUM ANALYZE keeper_health');
117
+ await this.pool.query('VACUUM ANALYZE keeper_rounds');
118
+
119
+ console.log(' āœ… VACUUM complete');
120
+ }
121
+
122
+ async run() {
123
+ console.log('🧹 DATABASE CLEANUP STARTING');
124
+ console.log('='.repeat(80));
125
+ console.log(`Time: ${new Date().toISOString()}`);
126
+ console.log();
127
+
128
+ try {
129
+ // Show stats before cleanup
130
+ console.log('šŸ“Š Before Cleanup:');
131
+ const statsBefore = await this.getTableStats();
132
+ for (const [table, stats] of Object.entries(statsBefore)) {
133
+ console.log(` ${table}: ${stats.rows.toLocaleString()} rows (${stats.size})`);
134
+ }
135
+
136
+ // Cleanup
137
+ const actionsDeleted = await this.cleanupKeeperActions();
138
+ const healthDeleted = await this.cleanupKeeperHealth();
139
+ const roundsArchived = await this.archiveOldIncompleteRounds();
140
+
141
+ // Vacuum
142
+ await this.vacuum();
143
+
144
+ // Show stats after cleanup
145
+ console.log('\nšŸ“Š After Cleanup:');
146
+ const statsAfter = await this.getTableStats();
147
+ for (const [table, stats] of Object.entries(statsAfter)) {
148
+ const before = statsBefore[table].rows;
149
+ const after = stats.rows;
150
+ const diff = before - after;
151
+ const diffStr = diff > 0 ? ` (-${diff})` : '';
152
+ console.log(` ${table}: ${stats.rows.toLocaleString()} rows${diffStr} (${stats.size})`);
153
+ }
154
+
155
+ // Summary
156
+ console.log('\n' + '='.repeat(80));
157
+ console.log('āœ… CLEANUP COMPLETE');
158
+ console.log('='.repeat(80));
159
+ console.log(`
160
+ Summary:
161
+ - ${actionsDeleted} action logs deleted
162
+ - ${healthDeleted} health snapshots deleted
163
+ - ${roundsArchived} incomplete rounds archived
164
+ - keeper_rounds (resolved): Kept forever āœ…
165
+ - jackpot_rounds: Kept forever āœ…
166
+
167
+ Retention Policies:
168
+ - keeper_actions: ${RETENTION_POLICIES.keeper_actions} days
169
+ - keeper_health: ${RETENTION_POLICIES.keeper_health} days
170
+ - keeper_rounds (incomplete): ${RETENTION_POLICIES.keeper_rounds_incomplete} days
171
+ - keeper_rounds (resolved): Forever (audit trail)
172
+ - jackpot_rounds: Forever (user verification)
173
+ `);
174
+ console.log('='.repeat(80));
175
+
176
+ } catch (error) {
177
+ console.error('\nāŒ CLEANUP FAILED:', error.message);
178
+ console.error(error);
179
+ process.exit(1);
180
+ } finally {
181
+ await this.pool.end();
182
+ }
183
+ }
184
+ }
185
+
186
+ // Run if called directly
187
+ if (require.main === module) {
188
+ const cleaner = new DatabaseCleaner();
189
+ cleaner.run();
190
+ }
191
+
192
+ module.exports = DatabaseCleaner;
193
+
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Clear all notification caches from Redis
3
+ *
4
+ * Usage:
5
+ * REDIS_URL=<your-redis-url> node scripts/clear-notification-cache.js
6
+ *
7
+ * Or via Heroku:
8
+ * heroku run node scripts/clear-notification-cache.js -a <app-name>
9
+ */
10
+
11
+ const Redis = require('ioredis');
12
+
13
+ async function clearNotificationCache() {
14
+ const redisUrl = process.env.REDIS_URL;
15
+
16
+ if (!redisUrl) {
17
+ console.error('āŒ REDIS_URL environment variable is required');
18
+ console.log('Usage: REDIS_URL=<your-redis-url> node scripts/clear-notification-cache.js');
19
+ process.exit(1);
20
+ }
21
+
22
+ console.log('šŸ”— Connecting to Redis...');
23
+
24
+ const client = new Redis(redisUrl, {
25
+ tls: redisUrl.startsWith('rediss://') ? { rejectUnauthorized: false } : undefined,
26
+ });
27
+
28
+ try {
29
+ await client.ping();
30
+ console.log('āœ… Connected to Redis\n');
31
+
32
+ // Find and delete all notification-related keys
33
+ const patterns = [
34
+ 'notifications:*', // User notification sorted sets
35
+ 'notification:*', // Individual notification hashes
36
+ 'unread:*', // Unread counts
37
+ ];
38
+
39
+ let totalDeleted = 0;
40
+
41
+ for (const pattern of patterns) {
42
+ console.log(`šŸ” Scanning for keys matching: ${pattern}`);
43
+
44
+ let cursor = '0';
45
+ let keysToDelete = [];
46
+
47
+ // Use SCAN to find keys (safe for production)
48
+ do {
49
+ const [newCursor, keys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
50
+ cursor = newCursor;
51
+ keysToDelete = keysToDelete.concat(keys);
52
+ } while (cursor !== '0');
53
+
54
+ if (keysToDelete.length > 0) {
55
+ console.log(` Found ${keysToDelete.length} keys`);
56
+
57
+ // Delete in batches
58
+ const batchSize = 100;
59
+ for (let i = 0; i < keysToDelete.length; i += batchSize) {
60
+ const batch = keysToDelete.slice(i, i + batchSize);
61
+ await client.del(...batch);
62
+ }
63
+
64
+ totalDeleted += keysToDelete.length;
65
+ console.log(` āœ… Deleted ${keysToDelete.length} keys`);
66
+ } else {
67
+ console.log(` No keys found`);
68
+ }
69
+ }
70
+
71
+ console.log(`\nšŸŽ‰ Done! Cleared ${totalDeleted} notification cache keys.`);
72
+ console.log('šŸ“ Users will have their caches rebuilt from PostgreSQL on next request.');
73
+
74
+ } catch (error) {
75
+ console.error('āŒ Error:', error.message);
76
+ process.exit(1);
77
+ } finally {
78
+ await client.quit();
79
+ console.log('\nšŸ”Œ Disconnected from Redis');
80
+ }
81
+ }
82
+
83
+ clearNotificationCache();
84
+
85
+
@@ -0,0 +1,50 @@
1
+ // Convert mnemonic to Solana keypair
2
+ const fs = require('fs');
3
+
4
+ // Your mnemonic
5
+ const mnemonic = process.argv[2] || 'abuse comfort amazing live royal auto target quick damage flavor rug dress';
6
+
7
+ console.log('šŸ”‘ Converting mnemonic to Solana keypair...\n');
8
+ console.log('Mnemonic:', mnemonic);
9
+ console.log('');
10
+
11
+ // Using solana-keygen programmatically
12
+ const { execSync } = require('child_process');
13
+
14
+ try {
15
+ // Create a temp file with the mnemonic
16
+ const tempFile = '/tmp/temp-mnemonic.txt';
17
+ fs.writeFileSync(tempFile, mnemonic);
18
+
19
+ // Use solana-keygen to recover
20
+ const output = execSync(`solana-keygen recover -o wallets/oracle-new.json --force 'prompt://key=0/0' < ${tempFile}`, {
21
+ stdio: ['pipe', 'pipe', 'pipe']
22
+ }).toString();
23
+
24
+ // Get public key
25
+ const pubkey = execSync('solana-keygen pubkey wallets/oracle-new.json').toString().trim();
26
+
27
+ console.log('āœ… Keypair recovered!');
28
+ console.log('Public Key:', pubkey);
29
+ console.log('');
30
+ console.log('Expected: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa');
31
+ console.log('Match:', pubkey === 'FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa' ? 'āœ… YES' : 'āŒ NO');
32
+ console.log('');
33
+ console.log('Saved to: wallets/oracle-new.json');
34
+ console.log('');
35
+ console.log('To use:');
36
+ console.log(' mv wallets/oracle-new.json wallets/oracle.json');
37
+
38
+ // Clean up
39
+ fs.unlinkSync(tempFile);
40
+
41
+ } catch (error) {
42
+ console.error('Error:', error.message);
43
+ console.log('\nšŸ“ MANUAL STEPS:\n');
44
+ console.log('1. Run this command:');
45
+ console.log(' solana-keygen recover -o wallets/oracle.json --force\n');
46
+ console.log('2. When prompted, enter your mnemonic phrase\n');
47
+ console.log('3. Press Enter for default derivation path\n');
48
+ console.log('4. Verify public key matches: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa\n');
49
+ }
50
+
@@ -0,0 +1,44 @@
1
+ -- Users table for wallet-based authentication
2
+ -- Run this to create the users table in your Postgres database
3
+
4
+ CREATE TABLE IF NOT EXISTS users (
5
+ id SERIAL PRIMARY KEY,
6
+ wallet_address VARCHAR(44) UNIQUE NOT NULL,
7
+ email VARCHAR(255),
8
+ username VARCHAR(50) NOT NULL,
9
+ avatar TEXT,
10
+ referral_code VARCHAR(50),
11
+ signature TEXT,
12
+ onboarding_complete BOOLEAN DEFAULT false,
13
+ created_at TIMESTAMP DEFAULT NOW(),
14
+ updated_at TIMESTAMP DEFAULT NOW()
15
+ );
16
+
17
+ -- Nonces table for secure signature verification (prevents replay attacks)
18
+ CREATE TABLE IF NOT EXISTS auth_nonces (
19
+ wallet_address VARCHAR(44) PRIMARY KEY,
20
+ nonce VARCHAR(64) NOT NULL,
21
+ expires_at TIMESTAMP NOT NULL,
22
+ used BOOLEAN DEFAULT false,
23
+ created_at TIMESTAMP DEFAULT NOW()
24
+ );
25
+
26
+ -- Index for faster wallet lookups
27
+ CREATE INDEX IF NOT EXISTS idx_users_wallet ON users(wallet_address);
28
+
29
+ -- Index for username lookups
30
+ CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
31
+
32
+ -- Index for nonce cleanup
33
+ CREATE INDEX IF NOT EXISTS idx_nonces_expires ON auth_nonces(expires_at);
34
+
35
+ COMMENT ON TABLE users IS 'User profiles with wallet-based authentication';
36
+ COMMENT ON COLUMN users.wallet_address IS 'Solana wallet public key (base58)';
37
+ COMMENT ON COLUMN users.signature IS 'Signature proving wallet ownership';
38
+ COMMENT ON COLUMN users.onboarding_complete IS 'Whether user has completed onboarding flow';
39
+
40
+ COMMENT ON TABLE auth_nonces IS 'One-time nonces for signature verification (prevents replay attacks)';
41
+ COMMENT ON COLUMN auth_nonces.nonce IS 'Cryptographically secure random nonce';
42
+ COMMENT ON COLUMN auth_nonces.used IS 'Whether this nonce has been used';
43
+ COMMENT ON COLUMN auth_nonces.expires_at IS 'Nonce expiration time (5 minutes)';
44
+