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,459 @@
1
+ /**
2
+ * 👥 Social/Friends API Routes
3
+ *
4
+ * User search, friend requests, friends management
5
+ */
6
+
7
+ const express = require('express');
8
+ const router = express.Router();
9
+ const { authenticate } = require('../middleware/authenticate');
10
+ const { pool } = require('../services/db'); // Shared database pool
11
+
12
+ module.exports = (socialService) => {
13
+
14
+ /**
15
+ * GET /social/leaderboard
16
+ * Public leaderboard for modal (no auth required)
17
+ * Query: ?gameType=connect4|sports|esports&tab=wins|winrate|earnings|grinder|efficiency&limit=25
18
+ */
19
+ router.get('/leaderboard', async (req, res) => {
20
+ try {
21
+ const { gameType = 'connect4', tab = 'wins', limit = 25 } = req.query;
22
+ const result = await socialService.getLeaderboard(gameType, tab, limit);
23
+
24
+ res.json({
25
+ success: true,
26
+ ...result
27
+ });
28
+ } catch (error) {
29
+ console.error('Error getting leaderboard:', error);
30
+ res.status(500).json({
31
+ success: false,
32
+ error: error.message
33
+ });
34
+ }
35
+ });
36
+
37
+ /**
38
+ * GET /social/top-players
39
+ * Get top players by wins for each category (sports, connect4)
40
+ */
41
+ router.get('/top-players', authenticate, async (req, res) => {
42
+ try {
43
+ const { userId } = req.user;
44
+ const limit = Math.min(parseInt(req.query.limit) || 10, 20);
45
+
46
+ const topPlayers = await socialService.getTopPlayers(userId, limit);
47
+
48
+ res.json({
49
+ success: true,
50
+ ...topPlayers
51
+ });
52
+ } catch (error) {
53
+ console.error('Error getting top players:', error);
54
+ res.status(500).json({
55
+ success: false,
56
+ error: error.message
57
+ });
58
+ }
59
+ });
60
+
61
+ /**
62
+ * GET /social/discover/summary
63
+ * Get category summary with user counts for discover page
64
+ */
65
+ router.get('/discover/summary', authenticate, async (req, res) => {
66
+ try {
67
+ const { userId } = req.user;
68
+ const categories = await socialService.getCategorySummary(userId);
69
+
70
+ res.json({
71
+ success: true,
72
+ categories
73
+ });
74
+ } catch (error) {
75
+ console.error('Error getting category summary:', error);
76
+ res.status(500).json({
77
+ success: false,
78
+ error: error.message
79
+ });
80
+ }
81
+ });
82
+
83
+ /**
84
+ * GET /social/discover?category=NBA&page=1&limit=20&sortBy=activity
85
+ * Discover users by game category with pagination
86
+ *
87
+ * Categories: NBA, NHL, NFL, EPL, UFC, connect4
88
+ * SortBy: activity (default), games_played, recent
89
+ */
90
+ router.get('/discover', authenticate, async (req, res) => {
91
+ try {
92
+ const { category, page = 1, limit = 20, sortBy = 'activity' } = req.query;
93
+ const { userId } = req.user;
94
+
95
+ if (!category) {
96
+ return res.status(400).json({
97
+ success: false,
98
+ error: 'Category is required. Valid categories: NBA, NHL, NFL, EPL, UFC, connect4'
99
+ });
100
+ }
101
+
102
+ const validCategories = ['NBA', 'NHL', 'NFL', 'EPL', 'UFC', 'connect4', 'all'];
103
+ if (!validCategories.includes(category)) {
104
+ return res.status(400).json({
105
+ success: false,
106
+ error: `Invalid category. Valid categories: ${validCategories.join(', ')}`
107
+ });
108
+ }
109
+
110
+ const result = await socialService.discoverByCategory(category, userId, {
111
+ page: parseInt(page),
112
+ limit: parseInt(limit),
113
+ sortBy
114
+ });
115
+
116
+ res.json({
117
+ success: true,
118
+ category,
119
+ ...result
120
+ });
121
+ } catch (error) {
122
+ console.error('Error discovering users:', error);
123
+ res.status(500).json({
124
+ success: false,
125
+ error: error.message
126
+ });
127
+ }
128
+ });
129
+
130
+ /**
131
+ * GET /social/search?q=username
132
+ * Search users by username (empty query returns all users)
133
+ */
134
+ router.get('/search', authenticate, async (req, res) => {
135
+ try {
136
+ const { q } = req.query;
137
+ const { userId } = req.user;
138
+
139
+ // Empty query returns all users
140
+ const query = q || '';
141
+
142
+ const results = await socialService.searchUsers(query, userId);
143
+
144
+ res.json({
145
+ success: true,
146
+ results
147
+ });
148
+ } catch (error) {
149
+ console.error('Error searching users:', error);
150
+ res.status(500).json({
151
+ success: false,
152
+ error: error.message
153
+ });
154
+ }
155
+ });
156
+
157
+ /**
158
+ * POST /social/friend-request/:targetUserId
159
+ * Send friend request
160
+ */
161
+ router.post('/friend-request/:targetUserId', authenticate, async (req, res) => {
162
+ try {
163
+ const { userId } = req.user;
164
+ const targetUserId = parseInt(req.params.targetUserId);
165
+
166
+ if (userId === targetUserId) {
167
+ return res.status(400).json({
168
+ success: false,
169
+ error: 'Cannot send friend request to yourself'
170
+ });
171
+ }
172
+
173
+ const result = await socialService.sendFriendRequest(userId, targetUserId);
174
+
175
+ if (!result || !result.requestId) {
176
+ return res.status(500).json({
177
+ success: false,
178
+ error: 'Failed to send friend request'
179
+ });
180
+ }
181
+
182
+ const { requestId, notificationId } = result;
183
+
184
+ console.log(`[Social] Friend request sent from ${userId} to ${targetUserId}, requestId: ${requestId}, notificationId: ${notificationId}`);
185
+
186
+ // Send real-time notification via user's room (works whether online or not)
187
+ const chatNamespace = global.chatNamespace;
188
+
189
+ if (chatNamespace) {
190
+ // Get sender info (using shared pool)
191
+ const userResult = await pool.query(
192
+ 'SELECT username, wallet_address, avatar FROM users WHERE id = $1',
193
+ [userId]
194
+ );
195
+
196
+ if (userResult.rows.length > 0) {
197
+ const notificationPayload = {
198
+ id: notificationId, // Real database ID
199
+ friendRequestId: requestId,
200
+ type: 'friend_request',
201
+ read: false,
202
+ message: '',
203
+ senderUsername: userResult.rows[0].username,
204
+ senderWallet: userResult.rows[0].wallet_address,
205
+ senderAvatar: userResult.rows[0].avatar || null,
206
+ createdAt: new Date(),
207
+ };
208
+
209
+ console.log('[Social] Sending friend request notification to room user-' + targetUserId);
210
+ chatNamespace.to(`user-${targetUserId}`).emit('notification', notificationPayload);
211
+ console.log('[Social] Notification sent successfully');
212
+ }
213
+ } else {
214
+ console.log('[Social] WebSocket not available');
215
+ }
216
+
217
+ res.json({
218
+ success: true,
219
+ requestId
220
+ });
221
+ } catch (error) {
222
+ console.error('Error sending friend request:', error);
223
+ res.status(500).json({
224
+ success: false,
225
+ error: error.message
226
+ });
227
+ }
228
+ });
229
+
230
+ /**
231
+ * GET /social/friend-requests
232
+ * Get pending friend requests (received)
233
+ */
234
+ router.get('/friend-requests', authenticate, async (req, res) => {
235
+ try {
236
+ const { userId } = req.user;
237
+ const requests = await socialService.getPendingRequests(userId);
238
+
239
+ res.json({
240
+ success: true,
241
+ requests
242
+ });
243
+ } catch (error) {
244
+ console.error('Error getting friend requests:', error);
245
+ res.status(500).json({
246
+ success: false,
247
+ error: error.message
248
+ });
249
+ }
250
+ });
251
+
252
+ /**
253
+ * POST /social/request/:requestId/accept
254
+ * Accept friend request
255
+ */
256
+ router.post('/request/:requestId/accept', authenticate, async (req, res) => {
257
+ try {
258
+ const { userId } = req.user;
259
+ const requestId = parseInt(req.params.requestId);
260
+
261
+ const result = await socialService.acceptFriendRequest(requestId, userId);
262
+
263
+ if (!result || !result.success) {
264
+ return res.status(404).json({
265
+ success: false,
266
+ error: 'Friend request not found or already processed'
267
+ });
268
+ }
269
+
270
+ // Notify the sender that their request was accepted
271
+ const chatNamespace = global.chatNamespace;
272
+
273
+ if (chatNamespace && result.fromUserId) {
274
+ // Using shared pool
275
+ const userResult = await pool.query(
276
+ 'SELECT username, wallet_address, avatar FROM users WHERE id = $1',
277
+ [userId]
278
+ );
279
+
280
+ // Send WebSocket event for UI update via user's room
281
+ chatNamespace.to(`user-${result.fromUserId}`).emit('friend_request_accepted', {
282
+ requestId,
283
+ acceptedBy: userId,
284
+ acceptedByUsername: userResult.rows[0]?.username,
285
+ });
286
+
287
+ // Send notification that shows in bell badge (with real DB ID)
288
+ chatNamespace.to(`user-${result.fromUserId}`).emit('notification', {
289
+ id: result.notificationId, // Real database ID
290
+ type: 'friend_request_accepted',
291
+ read: false,
292
+ message: '',
293
+ senderUsername: userResult.rows[0]?.username || 'Someone',
294
+ senderWallet: userResult.rows[0]?.wallet_address || '',
295
+ senderAvatar: userResult.rows[0]?.avatar || null,
296
+ createdAt: new Date(),
297
+ });
298
+ }
299
+
300
+ res.json({
301
+ success: true
302
+ });
303
+ } catch (error) {
304
+ console.error('Error accepting friend request:', error);
305
+ res.status(500).json({
306
+ success: false,
307
+ error: error.message
308
+ });
309
+ }
310
+ });
311
+
312
+ /**
313
+ * POST /social/request/:requestId/reject
314
+ * Reject friend request
315
+ */
316
+ router.post('/request/:requestId/reject', authenticate, async (req, res) => {
317
+ try {
318
+ console.log('[Social] Reject request endpoint hit, requestId:', req.params.requestId);
319
+ const { userId } = req.user;
320
+ const requestId = parseInt(req.params.requestId);
321
+ console.log('[Social] Parsed requestId:', requestId, 'userId:', userId);
322
+
323
+ const result = await socialService.rejectFriendRequest(requestId, userId);
324
+ console.log('[Social] Reject result:', result);
325
+
326
+ if (!result || !result.success) {
327
+ return res.status(404).json({
328
+ success: false,
329
+ error: 'Friend request not found'
330
+ });
331
+ }
332
+
333
+ // Notify the sender that their request was declined
334
+ const chatNamespace = global.chatNamespace;
335
+
336
+ if (chatNamespace && result.fromUserId) {
337
+ // Using shared pool
338
+ const userResult = await pool.query(
339
+ 'SELECT username, wallet_address, avatar FROM users WHERE id = $1',
340
+ [userId]
341
+ );
342
+
343
+ // Send WebSocket event for UI update via user's room
344
+ chatNamespace.to(`user-${result.fromUserId}`).emit('friend_request_declined', {
345
+ requestId,
346
+ declinedBy: userId,
347
+ });
348
+
349
+ // Send notification that shows in bell badge (with real DB ID)
350
+ chatNamespace.to(`user-${result.fromUserId}`).emit('notification', {
351
+ id: result.notificationId, // Real database ID
352
+ type: 'friend_request_declined',
353
+ read: false,
354
+ message: '',
355
+ senderUsername: userResult.rows[0]?.username || 'Someone',
356
+ senderWallet: userResult.rows[0]?.wallet_address || '',
357
+ senderAvatar: userResult.rows[0]?.avatar || null,
358
+ createdAt: new Date(),
359
+ });
360
+ }
361
+
362
+ res.json({
363
+ success: true
364
+ });
365
+ } catch (error) {
366
+ console.error('Error rejecting friend request:', error);
367
+ res.status(500).json({
368
+ success: false,
369
+ error: error.message
370
+ });
371
+ }
372
+ });
373
+
374
+ /**
375
+ * GET /social/friends
376
+ * Get friends list
377
+ */
378
+ router.get('/friends', authenticate, async (req, res) => {
379
+ try {
380
+ const { userId } = req.user;
381
+ const friends = await socialService.getFriends(userId);
382
+
383
+ res.json({
384
+ success: true,
385
+ friends
386
+ });
387
+ } catch (error) {
388
+ console.error('Error getting friends:', error);
389
+ res.status(500).json({
390
+ success: false,
391
+ error: error.message
392
+ });
393
+ }
394
+ });
395
+
396
+ /**
397
+ * DELETE /social/friend/:targetUserId
398
+ * Remove friend
399
+ */
400
+ router.delete('/friend/:targetUserId', authenticate, async (req, res) => {
401
+ try {
402
+ const { userId } = req.user;
403
+ const targetUserId = parseInt(req.params.targetUserId);
404
+
405
+ const result = await socialService.removeFriend(userId, targetUserId);
406
+
407
+ if (!result.success) {
408
+ return res.status(500).json({
409
+ success: false,
410
+ error: 'Failed to remove friend'
411
+ });
412
+ }
413
+
414
+ // Notify the other user (silent - no notification, just UI update)
415
+ const chatNamespace = global.chatNamespace;
416
+
417
+ if (chatNamespace) {
418
+ chatNamespace.to(`user-${targetUserId}`).emit('friend_removed', {
419
+ removedBy: userId,
420
+ });
421
+ }
422
+
423
+ res.json({
424
+ success: true
425
+ });
426
+ } catch (error) {
427
+ console.error('Error removing friend:', error);
428
+ res.status(500).json({
429
+ success: false,
430
+ error: error.message
431
+ });
432
+ }
433
+ });
434
+
435
+ /**
436
+ * GET /social/blocked
437
+ * Get blocked users list
438
+ */
439
+ router.get('/blocked', authenticate, async (req, res) => {
440
+ try {
441
+ const { userId } = req.user;
442
+ const blocked = await socialService.getBlockedUsers(userId);
443
+
444
+ res.json({
445
+ success: true,
446
+ blocked
447
+ });
448
+ } catch (error) {
449
+ console.error('Error getting blocked users:', error);
450
+ res.status(500).json({
451
+ success: false,
452
+ error: error.message
453
+ });
454
+ }
455
+ });
456
+
457
+ return router;
458
+ };
459
+