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,214 @@
1
+ /**
2
+ * 📱 Device Settings API Routes
3
+ *
4
+ * Public endpoints for device-level settings (no authentication required)
5
+ * Used for cross-browser state like "Add to Home Screen" banner dismissal
6
+ */
7
+
8
+ const express = require('express');
9
+ const router = express.Router();
10
+ const { pool } = require('../services/db');
11
+
12
+ // Initialize device_settings table
13
+ async function initializeDeviceTable() {
14
+ if (!process.env.DATABASE_URL) {
15
+ console.log('⚠️ Device settings table skipped: DATABASE_URL not set');
16
+ return;
17
+ }
18
+
19
+ try {
20
+ // Create table if it doesn't exist
21
+ await pool.query(`
22
+ CREATE TABLE IF NOT EXISTS device_settings (
23
+ id SERIAL PRIMARY KEY,
24
+ device_id VARCHAR(255) UNIQUE NOT NULL,
25
+ wallet_address VARCHAR(44),
26
+ dismissed_a2hs BOOLEAN DEFAULT false,
27
+ platform VARCHAR(50),
28
+ user_agent TEXT,
29
+ last_seen TIMESTAMP DEFAULT NOW(),
30
+ created_at TIMESTAMP DEFAULT NOW()
31
+ );
32
+ `);
33
+
34
+ // Add wallet_address column if it doesn't exist (migration for existing tables)
35
+ await pool.query(`
36
+ DO $$
37
+ BEGIN
38
+ IF NOT EXISTS (
39
+ SELECT 1 FROM information_schema.columns
40
+ WHERE table_name = 'device_settings'
41
+ AND column_name = 'wallet_address'
42
+ ) THEN
43
+ ALTER TABLE device_settings ADD COLUMN wallet_address VARCHAR(44);
44
+ RAISE NOTICE 'Added wallet_address column to device_settings';
45
+ END IF;
46
+ END $$;
47
+ `);
48
+
49
+ // Create indexes
50
+ await pool.query(`
51
+ CREATE INDEX IF NOT EXISTS idx_device_id ON device_settings(device_id);
52
+ CREATE INDEX IF NOT EXISTS idx_wallet_address ON device_settings(wallet_address);
53
+ `);
54
+
55
+ console.log('✅ Device settings table ready');
56
+ } catch (error) {
57
+ console.error('❌ Failed to initialize device_settings table:', error);
58
+ }
59
+ }
60
+
61
+ initializeDeviceTable();
62
+
63
+ /**
64
+ * GET /api/device/check-dismissal
65
+ * Check if A2HS was dismissed for this wallet
66
+ * Query params: walletAddress (REQUIRED)
67
+ */
68
+ router.get('/check-dismissal', async (req, res) => {
69
+ try {
70
+ const { walletAddress } = req.query;
71
+
72
+ if (!walletAddress) {
73
+ return res.status(400).json({ error: 'walletAddress is required' });
74
+ }
75
+
76
+ const result = await pool.query(
77
+ 'SELECT * FROM device_settings WHERE wallet_address = $1 AND dismissed_a2hs = true',
78
+ [walletAddress]
79
+ );
80
+
81
+ if (result.rows.length > 0) {
82
+ return res.json({
83
+ dismissedA2HS: true,
84
+ lastSeen: result.rows[0].last_seen,
85
+ });
86
+ }
87
+
88
+ res.json({
89
+ dismissedA2HS: false,
90
+ lastSeen: null,
91
+ });
92
+ } catch (error) {
93
+ console.error('[DeviceRoutes] Error fetching device settings:', error);
94
+ res.status(500).json({ error: 'Failed to fetch device settings' });
95
+ }
96
+ });
97
+
98
+ /**
99
+ * POST /api/device/dismiss-a2hs
100
+ * Mark "Add to Home Screen" banner as dismissed for this wallet
101
+ *
102
+ * Body: {
103
+ * walletAddress: string (REQUIRED),
104
+ * platform?: string (ios/android),
105
+ * userAgent?: string
106
+ * }
107
+ */
108
+ router.post('/dismiss-a2hs', async (req, res) => {
109
+ try {
110
+ const { walletAddress, platform, userAgent } = req.body;
111
+
112
+ if (!walletAddress) {
113
+ return res.status(400).json({ error: 'walletAddress is required' });
114
+ }
115
+
116
+ // Upsert: Insert or update if exists
117
+ await pool.query(`
118
+ INSERT INTO device_settings (device_id, wallet_address, dismissed_a2hs, platform, user_agent, last_seen)
119
+ VALUES ($1, $2, true, $3, $4, NOW())
120
+ ON CONFLICT (device_id)
121
+ DO UPDATE SET
122
+ dismissed_a2hs = true,
123
+ platform = COALESCE($3, device_settings.platform),
124
+ user_agent = COALESCE($4, device_settings.user_agent),
125
+ last_seen = NOW()
126
+ `, [`wallet_${walletAddress}`, walletAddress, platform, userAgent]);
127
+
128
+ console.log(`[DeviceRoutes] ✅ A2HS dismissed for wallet: ${walletAddress.slice(0, 8)}...`);
129
+
130
+ res.json({
131
+ success: true,
132
+ dismissedA2HS: true
133
+ });
134
+ } catch (error) {
135
+ console.error('[DeviceRoutes] ❌ Error dismissing A2HS:', error);
136
+ res.status(500).json({ error: 'Failed to save dismissal' });
137
+ }
138
+ });
139
+
140
+ /**
141
+ * POST /api/device/link-wallet
142
+ * Link a wallet address to the current device (by IP)
143
+ * Called when user authenticates - links their wallet to any IP-based dismissals
144
+ *
145
+ * Body: {
146
+ * walletAddress: string
147
+ * }
148
+ */
149
+ router.post('/link-wallet', async (req, res) => {
150
+ try {
151
+ const { walletAddress } = req.body;
152
+
153
+ if (!walletAddress) {
154
+ return res.status(400).json({ error: 'walletAddress is required' });
155
+ }
156
+
157
+ const ipAddress = req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
158
+ req.headers['x-real-ip'] ||
159
+ req.connection.remoteAddress ||
160
+ req.socket.remoteAddress;
161
+
162
+ if (!ipAddress) {
163
+ return res.status(400).json({ error: 'Unable to determine IP address' });
164
+ }
165
+
166
+ const deviceId = `ip_${ipAddress}`;
167
+
168
+ // Update the current IP's record with the wallet address
169
+ await pool.query(`
170
+ UPDATE device_settings
171
+ SET wallet_address = $1, last_seen = NOW()
172
+ WHERE device_id = $2
173
+ `, [walletAddress, deviceId]);
174
+
175
+ console.log(`[DeviceRoutes] ✅ Linked wallet ${walletAddress.slice(0, 8)}... to IP: ${ipAddress}`);
176
+
177
+ res.json({
178
+ success: true,
179
+ walletAddress,
180
+ deviceId
181
+ });
182
+ } catch (error) {
183
+ console.error('[DeviceRoutes] Error linking wallet:', error);
184
+ res.status(500).json({ error: 'Failed to link wallet' });
185
+ }
186
+ });
187
+
188
+ /**
189
+ * DELETE /api/device/:deviceId/dismiss-a2hs
190
+ * Reset dismissal (for testing or user request)
191
+ */
192
+ router.delete('/:deviceId/dismiss-a2hs', async (req, res) => {
193
+ try {
194
+ const { deviceId } = req.params;
195
+
196
+ await pool.query(
197
+ 'UPDATE device_settings SET dismissed_a2hs = false, last_seen = NOW() WHERE device_id = $1',
198
+ [deviceId]
199
+ );
200
+
201
+ console.log(`[DeviceRoutes] ✅ A2HS dismissal reset for device: ${deviceId.slice(0, 8)}...`);
202
+
203
+ res.json({
204
+ success: true,
205
+ deviceId,
206
+ dismissedA2HS: false
207
+ });
208
+ } catch (error) {
209
+ console.error('[DeviceRoutes] Error resetting dismissal:', error);
210
+ res.status(500).json({ error: 'Failed to reset dismissal' });
211
+ }
212
+ });
213
+
214
+ module.exports = router;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * 💬 Direct Message Routes
3
+ *
4
+ * REST API endpoints for direct messages
5
+ */
6
+
7
+ const express = require('express');
8
+ const router = express.Router();
9
+ const { authenticate } = require('../middleware/authenticate');
10
+
11
+ // Will be injected from server.js
12
+ let dmService = null;
13
+
14
+ /**
15
+ * Initialize routes with dependencies
16
+ */
17
+ function initializeDMRoutes(dmServiceInstance) {
18
+ dmService = dmServiceInstance;
19
+ return router;
20
+ }
21
+
22
+ /**
23
+ * GET /dm/conversations
24
+ * Get all conversations for the current user (inbox)
25
+ */
26
+ router.get('/conversations', authenticate, async (req, res) => {
27
+ try {
28
+ const userId = req.user.userId;
29
+ const limit = parseInt(req.query.limit) || 20;
30
+
31
+ const conversations = await dmService.getConversations(userId, limit);
32
+ res.json({ conversations });
33
+ } catch (error) {
34
+ console.error('[DM Routes] Error getting conversations:', error);
35
+ res.status(500).json({ error: 'Failed to get conversations' });
36
+ }
37
+ });
38
+
39
+ /**
40
+ * GET /dm/conversation/:walletAddress
41
+ * Get conversation history with a specific user
42
+ */
43
+ router.get('/conversation/:walletAddress', authenticate, async (req, res) => {
44
+ try {
45
+ const userId = req.user.userId;
46
+ const { walletAddress } = req.params;
47
+ const limit = parseInt(req.query.limit) || 50;
48
+ const beforeId = req.query.beforeId ? parseInt(req.query.beforeId) : null;
49
+
50
+ // Get other user's ID from wallet
51
+ const otherUser = await dmService.getUserByWallet(walletAddress);
52
+ if (!otherUser) {
53
+ return res.status(404).json({ error: 'User not found' });
54
+ }
55
+
56
+ const messages = await dmService.getConversation(userId, otherUser.id, limit, beforeId);
57
+
58
+ // Mark messages as read
59
+ await dmService.markAsRead(userId, otherUser.id);
60
+
61
+ res.json({
62
+ messages,
63
+ otherUser: {
64
+ id: otherUser.id,
65
+ username: otherUser.username,
66
+ avatar: otherUser.avatar,
67
+ walletAddress: otherUser.walletAddress,
68
+ }
69
+ });
70
+ } catch (error) {
71
+ console.error('[DM Routes] Error getting conversation:', error);
72
+ res.status(500).json({ error: 'Failed to get conversation' });
73
+ }
74
+ });
75
+
76
+ /**
77
+ * POST /dm/send
78
+ * Send a direct message
79
+ */
80
+ router.post('/send', authenticate, async (req, res) => {
81
+ try {
82
+ const senderId = req.user.userId;
83
+ const { recipientWallet, message } = req.body;
84
+
85
+ if (!recipientWallet || !message) {
86
+ return res.status(400).json({ error: 'recipientWallet and message are required' });
87
+ }
88
+
89
+ // Get recipient's ID from wallet
90
+ const recipient = await dmService.getUserByWallet(recipientWallet);
91
+ if (!recipient) {
92
+ return res.status(404).json({ error: 'Recipient not found' });
93
+ }
94
+
95
+ if (recipient.id === senderId) {
96
+ return res.status(400).json({ error: 'Cannot send message to yourself' });
97
+ }
98
+
99
+ const dm = await dmService.sendMessage(senderId, recipient.id, message);
100
+ res.json({ message: dm });
101
+ } catch (error) {
102
+ console.error('[DM Routes] Error sending message:', error);
103
+ res.status(500).json({ error: error.message || 'Failed to send message' });
104
+ }
105
+ });
106
+
107
+ /**
108
+ * POST /dm/read/:walletAddress
109
+ * Mark all messages from a user as read
110
+ */
111
+ router.post('/read/:walletAddress', authenticate, async (req, res) => {
112
+ try {
113
+ const userId = req.user.userId;
114
+ const { walletAddress } = req.params;
115
+
116
+ const otherUser = await dmService.getUserByWallet(walletAddress);
117
+ if (!otherUser) {
118
+ return res.status(404).json({ error: 'User not found' });
119
+ }
120
+
121
+ const count = await dmService.markAsRead(userId, otherUser.id);
122
+ res.json({ markedRead: count });
123
+ } catch (error) {
124
+ console.error('[DM Routes] Error marking as read:', error);
125
+ res.status(500).json({ error: 'Failed to mark as read' });
126
+ }
127
+ });
128
+
129
+ /**
130
+ * GET /dm/unread
131
+ * Get unread DM count
132
+ */
133
+ router.get('/unread', authenticate, async (req, res) => {
134
+ try {
135
+ const userId = req.user.userId;
136
+ const count = await dmService.getUnreadCount(userId);
137
+ res.json({ unreadCount: count });
138
+ } catch (error) {
139
+ console.error('[DM Routes] Error getting unread count:', error);
140
+ res.status(500).json({ error: 'Failed to get unread count' });
141
+ }
142
+ });
143
+
144
+ /**
145
+ * DELETE /dm/conversation/:walletAddress
146
+ * Delete a conversation (soft delete for current user only)
147
+ */
148
+ router.delete('/conversation/:walletAddress', authenticate, async (req, res) => {
149
+ try {
150
+ const userId = req.user.userId;
151
+ const { walletAddress } = req.params;
152
+
153
+ const otherUser = await dmService.getUserByWallet(walletAddress);
154
+ if (!otherUser) {
155
+ return res.status(404).json({ error: 'User not found' });
156
+ }
157
+
158
+ await dmService.deleteConversation(userId, otherUser.id);
159
+ res.json({ success: true });
160
+ } catch (error) {
161
+ console.error('[DM Routes] Error deleting conversation:', error);
162
+ res.status(500).json({ error: 'Failed to delete conversation' });
163
+ }
164
+ });
165
+
166
+ module.exports = { initializeDMRoutes, router };
167
+