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.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Avatar Proxy Routes
|
|
3
|
+
* Caches external avatar images in Redis to avoid rate limiting
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const axios = require('axios');
|
|
9
|
+
const redisService = require('../services/redisService');
|
|
10
|
+
|
|
11
|
+
// Cache TTL: 24 hours
|
|
12
|
+
const AVATAR_CACHE_TTL = 60 * 60 * 24;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GET /api/avatar/proxy?url=<encoded_url>
|
|
16
|
+
* Proxies and caches external avatar images
|
|
17
|
+
*/
|
|
18
|
+
router.get('/proxy', async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const { url } = req.query;
|
|
21
|
+
|
|
22
|
+
if (!url) {
|
|
23
|
+
return res.status(400).json({ error: 'URL parameter required' });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const decodedUrl = decodeURIComponent(url);
|
|
27
|
+
|
|
28
|
+
// Validate URL is from allowed domains
|
|
29
|
+
const allowedDomains = ['api.dicebear.com', 'avatars.dicebear.com'];
|
|
30
|
+
const urlObj = new URL(decodedUrl);
|
|
31
|
+
if (!allowedDomains.some(domain => urlObj.hostname.includes(domain))) {
|
|
32
|
+
return res.status(403).json({ error: 'Domain not allowed' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create cache key from URL
|
|
36
|
+
const cacheKey = `avatar:${Buffer.from(decodedUrl).toString('base64').slice(0, 100)}`;
|
|
37
|
+
|
|
38
|
+
// Try to get from Redis cache
|
|
39
|
+
if (redisService.isAvailable()) {
|
|
40
|
+
const cached = await redisService.get(cacheKey);
|
|
41
|
+
if (cached) {
|
|
42
|
+
const data = JSON.parse(cached);
|
|
43
|
+
res.set('Content-Type', data.contentType);
|
|
44
|
+
res.set('Cache-Control', 'public, max-age=86400'); // Browser cache 24h
|
|
45
|
+
res.set('X-Cache', 'HIT');
|
|
46
|
+
return res.send(Buffer.from(data.image, 'base64'));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fetch from external service
|
|
51
|
+
const response = await axios.get(decodedUrl, {
|
|
52
|
+
responseType: 'arraybuffer',
|
|
53
|
+
timeout: 5000,
|
|
54
|
+
headers: {
|
|
55
|
+
'User-Agent': 'Dubs-Avatar-Proxy/1.0',
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const contentType = response.headers['content-type'] || 'image/svg+xml';
|
|
60
|
+
const imageBuffer = Buffer.from(response.data);
|
|
61
|
+
|
|
62
|
+
// Cache in Redis
|
|
63
|
+
if (redisService.isAvailable()) {
|
|
64
|
+
const cacheData = {
|
|
65
|
+
contentType,
|
|
66
|
+
image: imageBuffer.toString('base64'),
|
|
67
|
+
};
|
|
68
|
+
await redisService.set(cacheKey, JSON.stringify(cacheData), AVATAR_CACHE_TTL);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Return image
|
|
72
|
+
res.set('Content-Type', contentType);
|
|
73
|
+
res.set('Cache-Control', 'public, max-age=86400');
|
|
74
|
+
res.set('X-Cache', 'MISS');
|
|
75
|
+
res.send(imageBuffer);
|
|
76
|
+
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('[Avatar Proxy] Error:', error.message);
|
|
79
|
+
|
|
80
|
+
// Return default avatar on error
|
|
81
|
+
res.redirect('/default_avatar.png');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
module.exports = router;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bot Routes - Server-to-server endpoints for bot integration
|
|
3
|
+
* These endpoints are called BY the Telegram bot, not by users
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const express = require('express');
|
|
7
|
+
const router = express.Router();
|
|
8
|
+
const { pool } = require('../services/db'); // Shared database pool
|
|
9
|
+
|
|
10
|
+
// Bot secret for authentication
|
|
11
|
+
const BOT_SECRET = process.env.BOT_SECRET || 'dev-secret-change-in-prod';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Middleware: Verify request comes from bot
|
|
15
|
+
*/
|
|
16
|
+
function verifyBot(req, res, next) {
|
|
17
|
+
const botSecret = req.headers['x-bot-secret'];
|
|
18
|
+
|
|
19
|
+
if (!botSecret || botSecret !== BOT_SECRET) {
|
|
20
|
+
console.error('[BotRoutes] Unauthorized bot request');
|
|
21
|
+
return res.status(401).json({
|
|
22
|
+
success: false,
|
|
23
|
+
error: 'Unauthorized'
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
next();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* POST /api/bot/telegram-connection
|
|
32
|
+
* Called by bot when user completes /connect CODE
|
|
33
|
+
* Links Telegram account to wallet address
|
|
34
|
+
*/
|
|
35
|
+
router.post('/telegram-connection', verifyBot, async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
const {
|
|
38
|
+
walletAddress,
|
|
39
|
+
telegramUserId,
|
|
40
|
+
telegramUsername,
|
|
41
|
+
telegramFirstName,
|
|
42
|
+
telegramLastName,
|
|
43
|
+
telegramPhotoUrl,
|
|
44
|
+
} = req.body;
|
|
45
|
+
|
|
46
|
+
console.log('[BotRoutes] Telegram connection request:', {
|
|
47
|
+
walletAddress: walletAddress?.slice(0, 8) + '...',
|
|
48
|
+
telegramUserId,
|
|
49
|
+
telegramUsername,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Validate required fields
|
|
53
|
+
if (!walletAddress || !telegramUserId) {
|
|
54
|
+
return res.status(400).json({
|
|
55
|
+
success: false,
|
|
56
|
+
error: 'Missing walletAddress or telegramUserId'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if this Telegram account is already linked to another wallet
|
|
61
|
+
const existingLink = await pool.query(
|
|
62
|
+
'SELECT wallet_address FROM users WHERE telegram_user_id = $1 AND wallet_address != $2',
|
|
63
|
+
[telegramUserId, walletAddress]
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
if (existingLink.rows.length > 0) {
|
|
67
|
+
console.error('[BotRoutes] Telegram already linked to another wallet');
|
|
68
|
+
return res.status(400).json({
|
|
69
|
+
success: false,
|
|
70
|
+
error: 'This Telegram account is already linked to another wallet'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Update user with Telegram info
|
|
75
|
+
const result = await pool.query(
|
|
76
|
+
`UPDATE users
|
|
77
|
+
SET telegram_user_id = $1,
|
|
78
|
+
telegram_username = $2,
|
|
79
|
+
telegram_first_name = $3,
|
|
80
|
+
telegram_last_name = $4,
|
|
81
|
+
telegram_photo_url = $5,
|
|
82
|
+
telegram_connected_at = NOW(),
|
|
83
|
+
updated_at = NOW()
|
|
84
|
+
WHERE wallet_address = $6
|
|
85
|
+
RETURNING id, wallet_address, telegram_user_id, telegram_username,
|
|
86
|
+
telegram_first_name, telegram_last_name, telegram_photo_url,
|
|
87
|
+
telegram_connected_at`,
|
|
88
|
+
[
|
|
89
|
+
telegramUserId,
|
|
90
|
+
telegramUsername || null,
|
|
91
|
+
telegramFirstName || null,
|
|
92
|
+
telegramLastName || null,
|
|
93
|
+
telegramPhotoUrl || null,
|
|
94
|
+
walletAddress
|
|
95
|
+
]
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (result.rows.length === 0) {
|
|
99
|
+
console.error('[BotRoutes] User not found:', walletAddress);
|
|
100
|
+
return res.status(404).json({
|
|
101
|
+
success: false,
|
|
102
|
+
error: 'User not found'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const user = result.rows[0];
|
|
107
|
+
|
|
108
|
+
// Create default notification preferences if not exist
|
|
109
|
+
await pool.query(
|
|
110
|
+
`INSERT INTO telegram_notification_preferences (user_id)
|
|
111
|
+
VALUES ($1)
|
|
112
|
+
ON CONFLICT (user_id) DO NOTHING`,
|
|
113
|
+
[user.id]
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
console.log('[BotRoutes] ✅ Telegram linked successfully:', {
|
|
117
|
+
userId: user.id,
|
|
118
|
+
wallet: walletAddress.slice(0, 8) + '...',
|
|
119
|
+
telegramId: telegramUserId,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
res.json({
|
|
123
|
+
success: true,
|
|
124
|
+
message: 'Telegram account linked successfully',
|
|
125
|
+
user: {
|
|
126
|
+
id: user.id,
|
|
127
|
+
walletAddress: user.wallet_address,
|
|
128
|
+
telegram: {
|
|
129
|
+
userId: user.telegram_user_id,
|
|
130
|
+
username: user.telegram_username,
|
|
131
|
+
firstName: user.telegram_first_name,
|
|
132
|
+
lastName: user.telegram_last_name,
|
|
133
|
+
photoUrl: user.telegram_photo_url,
|
|
134
|
+
connectedAt: user.telegram_connected_at
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('[BotRoutes] Error linking Telegram:', error);
|
|
140
|
+
res.status(500).json({
|
|
141
|
+
success: false,
|
|
142
|
+
error: error.message
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* POST /api/bot/telegram-disconnection
|
|
149
|
+
* Called by bot when user wants to disconnect
|
|
150
|
+
*/
|
|
151
|
+
router.post('/telegram-disconnection', verifyBot, async (req, res) => {
|
|
152
|
+
try {
|
|
153
|
+
const { telegramUserId } = req.body;
|
|
154
|
+
|
|
155
|
+
if (!telegramUserId) {
|
|
156
|
+
return res.status(400).json({
|
|
157
|
+
success: false,
|
|
158
|
+
error: 'Missing telegramUserId'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result = await pool.query(
|
|
163
|
+
`UPDATE users
|
|
164
|
+
SET telegram_user_id = NULL,
|
|
165
|
+
telegram_username = NULL,
|
|
166
|
+
telegram_first_name = NULL,
|
|
167
|
+
telegram_last_name = NULL,
|
|
168
|
+
telegram_photo_url = NULL,
|
|
169
|
+
telegram_connected_at = NULL,
|
|
170
|
+
updated_at = NOW()
|
|
171
|
+
WHERE telegram_user_id = $1
|
|
172
|
+
RETURNING wallet_address`,
|
|
173
|
+
[telegramUserId]
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (result.rows.length === 0) {
|
|
177
|
+
return res.status(404).json({
|
|
178
|
+
success: false,
|
|
179
|
+
error: 'User not found'
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log('[BotRoutes] ✅ Telegram disconnected for user:', result.rows[0].wallet_address);
|
|
184
|
+
|
|
185
|
+
res.json({
|
|
186
|
+
success: true,
|
|
187
|
+
message: 'Telegram account disconnected successfully'
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('[BotRoutes] Error disconnecting Telegram:', error);
|
|
191
|
+
res.status(500).json({
|
|
192
|
+
success: false,
|
|
193
|
+
error: error.message
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* GET /api/bot/health
|
|
200
|
+
* Health check for bot integration
|
|
201
|
+
*/
|
|
202
|
+
router.get('/health', (req, res) => {
|
|
203
|
+
res.json({
|
|
204
|
+
success: true,
|
|
205
|
+
status: 'ok',
|
|
206
|
+
service: 'bot-routes'
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
module.exports = router;
|
|
211
|
+
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 💬 Chat API Routes (v2 - Production Ready)
|
|
3
|
+
*
|
|
4
|
+
* JWT-authenticated chat with replies, notifications, and relationships
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const express = require('express');
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
const { authenticate, optionalAuth } = require('../middleware/authenticate');
|
|
10
|
+
const { pool } = require('../services/db'); // Shared database pool
|
|
11
|
+
|
|
12
|
+
module.exports = (chatService, io) => {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GET /chat/messages
|
|
16
|
+
* Get recent chat messages (optionally authenticated to filter blocked users)
|
|
17
|
+
*/
|
|
18
|
+
router.get('/messages', optionalAuth, async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
21
|
+
const before = req.query.before ? parseInt(req.query.before) : null;
|
|
22
|
+
const userId = req.user?.userId || null;
|
|
23
|
+
|
|
24
|
+
const result = await chatService.getRecentMessages(limit, userId, before);
|
|
25
|
+
|
|
26
|
+
// Backwards compatible: if result is array (old behavior), wrap it
|
|
27
|
+
const messages = Array.isArray(result) ? result : result.messages;
|
|
28
|
+
const hasMore = Array.isArray(result) ? messages.length === limit : result.hasMore;
|
|
29
|
+
const oldestId = messages.length > 0 ? messages[0].id : null;
|
|
30
|
+
|
|
31
|
+
res.json({
|
|
32
|
+
success: true,
|
|
33
|
+
messages,
|
|
34
|
+
count: messages.length,
|
|
35
|
+
hasMore,
|
|
36
|
+
oldestId
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error('Error fetching messages:', error);
|
|
40
|
+
res.status(500).json({
|
|
41
|
+
success: false,
|
|
42
|
+
error: error.message
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* POST /chat/message
|
|
49
|
+
* Post a new message (requires authentication)
|
|
50
|
+
*/
|
|
51
|
+
router.post('/message', authenticate, async (req, res) => {
|
|
52
|
+
try {
|
|
53
|
+
const { message, replyToId } = req.body;
|
|
54
|
+
const { userId, walletAddress } = req.user;
|
|
55
|
+
|
|
56
|
+
if (!message) {
|
|
57
|
+
return res.status(400).json({
|
|
58
|
+
success: false,
|
|
59
|
+
error: 'Message is required'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Rate limiting: Max 5 messages per minute
|
|
64
|
+
const recentCount = await chatService.getRecentMessageCount(userId, 1);
|
|
65
|
+
if (recentCount >= 5) {
|
|
66
|
+
return res.status(429).json({
|
|
67
|
+
success: false,
|
|
68
|
+
error: 'Woah, slow down there 🤣'
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get user info from database (using shared pool)
|
|
73
|
+
const userResult = await pool.query(
|
|
74
|
+
'SELECT username, avatar FROM users WHERE id = $1',
|
|
75
|
+
[userId]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (userResult.rows.length === 0) {
|
|
79
|
+
return res.status(404).json({
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'User not found'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const { username, avatar } = userResult.rows[0];
|
|
86
|
+
|
|
87
|
+
// Add message to database
|
|
88
|
+
const savedMessage = await chatService.addMessage(
|
|
89
|
+
userId,
|
|
90
|
+
walletAddress,
|
|
91
|
+
username,
|
|
92
|
+
avatar,
|
|
93
|
+
message,
|
|
94
|
+
{ replyToId }
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// Broadcast to chat namespace (WebSocket handled separately)
|
|
98
|
+
// The WebSocket handler will emit this event
|
|
99
|
+
|
|
100
|
+
res.json({
|
|
101
|
+
success: true,
|
|
102
|
+
message: savedMessage
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Error posting message:', error);
|
|
106
|
+
res.status(500).json({
|
|
107
|
+
success: false,
|
|
108
|
+
error: error.message
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* DELETE /chat/message/:id
|
|
115
|
+
* Delete your own message
|
|
116
|
+
*/
|
|
117
|
+
router.delete('/message/:id', authenticate, async (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
const messageId = parseInt(req.params.id);
|
|
120
|
+
const { userId } = req.user;
|
|
121
|
+
|
|
122
|
+
const deleted = await chatService.deleteMessage(messageId, userId);
|
|
123
|
+
|
|
124
|
+
if (!deleted) {
|
|
125
|
+
return res.status(404).json({
|
|
126
|
+
success: false,
|
|
127
|
+
error: 'Message not found or unauthorized'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
res.json({
|
|
132
|
+
success: true,
|
|
133
|
+
messageId
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('Error deleting message:', error);
|
|
137
|
+
res.status(500).json({
|
|
138
|
+
success: false,
|
|
139
|
+
error: error.message
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* PUT /chat/message/:id
|
|
146
|
+
* Edit your own message
|
|
147
|
+
*/
|
|
148
|
+
router.put('/message/:id', authenticate, async (req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
const messageId = parseInt(req.params.id);
|
|
151
|
+
const { userId } = req.user;
|
|
152
|
+
const { message } = req.body;
|
|
153
|
+
|
|
154
|
+
if (!message) {
|
|
155
|
+
return res.status(400).json({
|
|
156
|
+
success: false,
|
|
157
|
+
error: 'Message is required'
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const edited = await chatService.editMessage(messageId, userId, message);
|
|
162
|
+
|
|
163
|
+
if (!edited) {
|
|
164
|
+
return res.status(404).json({
|
|
165
|
+
success: false,
|
|
166
|
+
error: 'Message not found or unauthorized'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
res.json({
|
|
171
|
+
success: true,
|
|
172
|
+
message: edited
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('Error editing message:', error);
|
|
176
|
+
res.status(500).json({
|
|
177
|
+
success: false,
|
|
178
|
+
error: error.message
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* GET /chat/notifications
|
|
185
|
+
* Get your notifications with cursor-based pagination
|
|
186
|
+
*
|
|
187
|
+
* Query params:
|
|
188
|
+
* - limit: Number of notifications per page (default: 10, max: 50)
|
|
189
|
+
* - cursor: Timestamp cursor for pagination (optional, for "load more")
|
|
190
|
+
*
|
|
191
|
+
* Response:
|
|
192
|
+
* - notifications: Array of notification objects
|
|
193
|
+
* - unreadCount: Total unread count
|
|
194
|
+
* - nextCursor: Cursor for next page (null if no more)
|
|
195
|
+
* - hasMore: Boolean indicating if more notifications exist
|
|
196
|
+
*/
|
|
197
|
+
router.get('/notifications', authenticate, async (req, res) => {
|
|
198
|
+
try {
|
|
199
|
+
const { userId } = req.user;
|
|
200
|
+
const limit = Math.min(parseInt(req.query.limit) || 10, 50); // Max 50 per request
|
|
201
|
+
const cursor = req.query.cursor ? parseInt(req.query.cursor) : null;
|
|
202
|
+
|
|
203
|
+
// Fetch paginated notifications
|
|
204
|
+
const result = await chatService.getNotifications(userId, { limit, cursor });
|
|
205
|
+
|
|
206
|
+
// Fetch unread count (uses Redis cache when available)
|
|
207
|
+
const unreadCount = await chatService.getUnreadCount(userId);
|
|
208
|
+
|
|
209
|
+
res.json({
|
|
210
|
+
success: true,
|
|
211
|
+
notifications: result.notifications,
|
|
212
|
+
unreadCount,
|
|
213
|
+
nextCursor: result.nextCursor,
|
|
214
|
+
hasMore: result.hasMore,
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error fetching notifications:', error);
|
|
218
|
+
res.status(500).json({
|
|
219
|
+
success: false,
|
|
220
|
+
error: error.message
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* POST /chat/notifications/read
|
|
227
|
+
* Mark notifications as read
|
|
228
|
+
*/
|
|
229
|
+
router.post('/notifications/read', authenticate, async (req, res) => {
|
|
230
|
+
try {
|
|
231
|
+
const { userId } = req.user;
|
|
232
|
+
const { notificationIds } = req.body;
|
|
233
|
+
|
|
234
|
+
if (!Array.isArray(notificationIds)) {
|
|
235
|
+
return res.status(400).json({
|
|
236
|
+
success: false,
|
|
237
|
+
error: 'notificationIds must be an array'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
await chatService.markNotificationsRead(userId, notificationIds);
|
|
242
|
+
|
|
243
|
+
res.json({
|
|
244
|
+
success: true
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('Error marking notifications read:', error);
|
|
248
|
+
res.status(500).json({
|
|
249
|
+
success: false,
|
|
250
|
+
error: error.message
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* POST /chat/block/:targetUserId
|
|
257
|
+
* Block a user
|
|
258
|
+
*/
|
|
259
|
+
router.post('/block/:targetUserId', authenticate, async (req, res) => {
|
|
260
|
+
try {
|
|
261
|
+
const { userId } = req.user;
|
|
262
|
+
const targetUserId = parseInt(req.params.targetUserId);
|
|
263
|
+
|
|
264
|
+
if (userId === targetUserId) {
|
|
265
|
+
return res.status(400).json({
|
|
266
|
+
success: false,
|
|
267
|
+
error: 'Cannot block yourself'
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
await chatService.blockUser(userId, targetUserId);
|
|
272
|
+
|
|
273
|
+
res.json({
|
|
274
|
+
success: true
|
|
275
|
+
});
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error('Error blocking user:', error);
|
|
278
|
+
res.status(500).json({
|
|
279
|
+
success: false,
|
|
280
|
+
error: error.message
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* DELETE /chat/block/:targetUserId
|
|
287
|
+
* Unblock a user
|
|
288
|
+
*/
|
|
289
|
+
router.delete('/block/:targetUserId', authenticate, async (req, res) => {
|
|
290
|
+
try {
|
|
291
|
+
const { userId } = req.user;
|
|
292
|
+
const targetUserId = parseInt(req.params.targetUserId);
|
|
293
|
+
|
|
294
|
+
await chatService.unblockUser(userId, targetUserId);
|
|
295
|
+
|
|
296
|
+
res.json({
|
|
297
|
+
success: true
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error('Error unblocking user:', error);
|
|
301
|
+
res.status(500).json({
|
|
302
|
+
success: false,
|
|
303
|
+
error: error.message
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* DELETE /chat/cleanup
|
|
310
|
+
* Cleanup old messages (protected - requires authentication)
|
|
311
|
+
* Note: Better to run as cron job instead of exposing as endpoint
|
|
312
|
+
*/
|
|
313
|
+
router.delete('/cleanup', authenticate, async (req, res) => {
|
|
314
|
+
try {
|
|
315
|
+
// Optional: Add admin check here if you want only admins to cleanup
|
|
316
|
+
// if (!req.user.isAdmin) {
|
|
317
|
+
// return res.status(403).json({ error: 'Admin only' });
|
|
318
|
+
// }
|
|
319
|
+
|
|
320
|
+
await chatService.cleanup();
|
|
321
|
+
res.json({
|
|
322
|
+
success: true,
|
|
323
|
+
message: 'Old messages cleaned up'
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.error('Error cleaning up:', error);
|
|
327
|
+
res.status(500).json({
|
|
328
|
+
success: false,
|
|
329
|
+
error: error.message
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* GET /chat/payments/user/:walletAddress
|
|
336
|
+
* Get all payment transactions for a user (sent and received)
|
|
337
|
+
* Used by transaction history to show chat payment context
|
|
338
|
+
*/
|
|
339
|
+
router.get('/payments/user/:walletAddress', async (req, res) => {
|
|
340
|
+
try {
|
|
341
|
+
const { walletAddress } = req.params;
|
|
342
|
+
|
|
343
|
+
// Fetch payments where user is sender OR recipient (using shared pool)
|
|
344
|
+
const result = await pool.query(`
|
|
345
|
+
SELECT
|
|
346
|
+
cp.*,
|
|
347
|
+
sender.username as sender_username,
|
|
348
|
+
sender.avatar as sender_avatar,
|
|
349
|
+
recipient.username as recipient_username,
|
|
350
|
+
recipient.avatar as recipient_avatar
|
|
351
|
+
FROM chat_payments cp
|
|
352
|
+
LEFT JOIN users sender ON cp.sender_user_id = sender.id
|
|
353
|
+
LEFT JOIN users recipient ON cp.recipient_user_id = recipient.id
|
|
354
|
+
WHERE cp.sender_wallet = $1 OR cp.recipient_wallet = $1
|
|
355
|
+
ORDER BY cp.created_at DESC
|
|
356
|
+
LIMIT 100
|
|
357
|
+
`, [walletAddress]);
|
|
358
|
+
|
|
359
|
+
console.log(`[chat/payments/user] Found ${result.rows.length} payments for ${walletAddress.slice(0, 8)}...`);
|
|
360
|
+
|
|
361
|
+
res.status(200).json({
|
|
362
|
+
success: true,
|
|
363
|
+
payments: result.rows
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('[chat/payments/user] Error:', error);
|
|
368
|
+
res.status(500).json({
|
|
369
|
+
success: false,
|
|
370
|
+
error: error.message
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
return router;
|
|
376
|
+
};
|
|
377
|
+
|