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,389 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📊 Player Stats Service
|
|
3
|
+
*
|
|
4
|
+
* Tracks player PNL, win/loss history, and provides chart data
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { pool } = require('./db'); // Shared database pool
|
|
8
|
+
const { LAMPORTS_PER_SOL } = require('@solana/web3.js');
|
|
9
|
+
|
|
10
|
+
class PlayerStatsService {
|
|
11
|
+
constructor() {
|
|
12
|
+
// Use shared pool from services/db.js
|
|
13
|
+
this.pool = pool;
|
|
14
|
+
|
|
15
|
+
this.initializeTables();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async initializeTables() {
|
|
19
|
+
try {
|
|
20
|
+
// Player stats table - overall performance with streak tracking
|
|
21
|
+
await this.pool.query(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS player_stats (
|
|
23
|
+
wallet_address VARCHAR(100) PRIMARY KEY,
|
|
24
|
+
total_wagered NUMERIC(20, 9) DEFAULT 0,
|
|
25
|
+
total_won NUMERIC(20, 9) DEFAULT 0,
|
|
26
|
+
net_pnl NUMERIC(20, 9) DEFAULT 0,
|
|
27
|
+
rounds_played INTEGER DEFAULT 0,
|
|
28
|
+
rounds_won INTEGER DEFAULT 0,
|
|
29
|
+
biggest_win NUMERIC(20, 9) DEFAULT 0,
|
|
30
|
+
biggest_win_round INTEGER,
|
|
31
|
+
current_win_streak INTEGER DEFAULT 0,
|
|
32
|
+
longest_win_streak INTEGER DEFAULT 0,
|
|
33
|
+
last_win_round INTEGER,
|
|
34
|
+
last_played TIMESTAMP,
|
|
35
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_player_stats_pnl ON player_stats(net_pnl DESC);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_player_stats_wagered ON player_stats(total_wagered DESC);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_player_stats_streak ON player_stats(current_win_streak DESC);
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
// Add streak columns if they don't exist (migration for existing tables)
|
|
44
|
+
await this.pool.query(`
|
|
45
|
+
DO $$
|
|
46
|
+
BEGIN
|
|
47
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'player_stats' AND column_name = 'current_win_streak') THEN
|
|
48
|
+
ALTER TABLE player_stats ADD COLUMN current_win_streak INTEGER DEFAULT 0;
|
|
49
|
+
END IF;
|
|
50
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'player_stats' AND column_name = 'longest_win_streak') THEN
|
|
51
|
+
ALTER TABLE player_stats ADD COLUMN longest_win_streak INTEGER DEFAULT 0;
|
|
52
|
+
END IF;
|
|
53
|
+
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'player_stats' AND column_name = 'last_win_round') THEN
|
|
54
|
+
ALTER TABLE player_stats ADD COLUMN last_win_round INTEGER;
|
|
55
|
+
END IF;
|
|
56
|
+
END $$;
|
|
57
|
+
`);
|
|
58
|
+
|
|
59
|
+
// Player history table - for chart data
|
|
60
|
+
await this.pool.query(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS player_history (
|
|
62
|
+
id SERIAL PRIMARY KEY,
|
|
63
|
+
wallet_address VARCHAR(100) NOT NULL,
|
|
64
|
+
round_id INTEGER NOT NULL,
|
|
65
|
+
action VARCHAR(20) NOT NULL,
|
|
66
|
+
amount NUMERIC(20, 9) NOT NULL,
|
|
67
|
+
cumulative_pnl NUMERIC(20, 9) NOT NULL,
|
|
68
|
+
timestamp TIMESTAMP DEFAULT NOW()
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_player_history_wallet ON player_history(wallet_address, timestamp DESC);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_player_history_round ON player_history(round_id);
|
|
73
|
+
`);
|
|
74
|
+
|
|
75
|
+
console.log('✅ Player stats tables initialized');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('❌ Failed to initialize player stats tables:', error.message);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Record a player entry (bet)
|
|
83
|
+
*/
|
|
84
|
+
async recordEntry({ walletAddress, roundId, amount }) {
|
|
85
|
+
try {
|
|
86
|
+
const amountSOL = amount / LAMPORTS_PER_SOL;
|
|
87
|
+
|
|
88
|
+
// Update or insert player stats
|
|
89
|
+
await this.pool.query(`
|
|
90
|
+
INSERT INTO player_stats (
|
|
91
|
+
wallet_address,
|
|
92
|
+
total_wagered,
|
|
93
|
+
rounds_played,
|
|
94
|
+
net_pnl,
|
|
95
|
+
last_played
|
|
96
|
+
)
|
|
97
|
+
VALUES ($1, $2::NUMERIC, 1, (0 - $2::NUMERIC), NOW())
|
|
98
|
+
ON CONFLICT (wallet_address) DO UPDATE SET
|
|
99
|
+
total_wagered = player_stats.total_wagered + $2::NUMERIC,
|
|
100
|
+
rounds_played = player_stats.rounds_played + 1,
|
|
101
|
+
net_pnl = player_stats.net_pnl - $2::NUMERIC,
|
|
102
|
+
last_played = NOW()
|
|
103
|
+
`, [walletAddress, amountSOL]);
|
|
104
|
+
|
|
105
|
+
// Get current cumulative PNL
|
|
106
|
+
const result = await this.pool.query(
|
|
107
|
+
'SELECT net_pnl FROM player_stats WHERE wallet_address = $1',
|
|
108
|
+
[walletAddress]
|
|
109
|
+
);
|
|
110
|
+
const cumulativePNL = parseFloat(result.rows[0].net_pnl);
|
|
111
|
+
|
|
112
|
+
// Add to history for chart
|
|
113
|
+
await this.pool.query(`
|
|
114
|
+
INSERT INTO player_history (
|
|
115
|
+
wallet_address,
|
|
116
|
+
round_id,
|
|
117
|
+
action,
|
|
118
|
+
amount,
|
|
119
|
+
cumulative_pnl
|
|
120
|
+
)
|
|
121
|
+
VALUES ($1, $2, 'entry', $3, $4)
|
|
122
|
+
`, [walletAddress, roundId, amountSOL, cumulativePNL]);
|
|
123
|
+
|
|
124
|
+
console.log(`📊 Entry recorded: ${walletAddress.slice(0, 8)}... wagered ◎${amountSOL}`);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Error recording entry:', error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Record a player win with streak tracking
|
|
133
|
+
*/
|
|
134
|
+
async recordWin({ walletAddress, roundId, winAmount }) {
|
|
135
|
+
try {
|
|
136
|
+
const winSOL = winAmount / LAMPORTS_PER_SOL;
|
|
137
|
+
const roundIdNum = parseInt(roundId);
|
|
138
|
+
|
|
139
|
+
// First, get current player stats to calculate streak
|
|
140
|
+
const existingStats = await this.pool.query(
|
|
141
|
+
'SELECT current_win_streak, last_win_round FROM player_stats WHERE wallet_address = $1',
|
|
142
|
+
[walletAddress]
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
let newStreak = 1;
|
|
146
|
+
if (existingStats.rows.length > 0) {
|
|
147
|
+
const { current_win_streak, last_win_round } = existingStats.rows[0];
|
|
148
|
+
// If they won the previous round, continue streak; otherwise reset to 1
|
|
149
|
+
if (last_win_round && roundIdNum === last_win_round + 1) {
|
|
150
|
+
newStreak = (current_win_streak || 0) + 1;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Update player stats with streak tracking
|
|
155
|
+
await this.pool.query(`
|
|
156
|
+
INSERT INTO player_stats (
|
|
157
|
+
wallet_address,
|
|
158
|
+
total_won,
|
|
159
|
+
rounds_won,
|
|
160
|
+
net_pnl,
|
|
161
|
+
biggest_win,
|
|
162
|
+
biggest_win_round,
|
|
163
|
+
current_win_streak,
|
|
164
|
+
longest_win_streak,
|
|
165
|
+
last_win_round,
|
|
166
|
+
last_played
|
|
167
|
+
)
|
|
168
|
+
VALUES ($1, $2, 1, $2, $2, $3, $4, $4, $3, NOW())
|
|
169
|
+
ON CONFLICT (wallet_address) DO UPDATE SET
|
|
170
|
+
total_won = player_stats.total_won + $2,
|
|
171
|
+
rounds_won = player_stats.rounds_won + 1,
|
|
172
|
+
net_pnl = player_stats.net_pnl + $2,
|
|
173
|
+
biggest_win = GREATEST(player_stats.biggest_win, $2),
|
|
174
|
+
biggest_win_round = CASE
|
|
175
|
+
WHEN $2 > player_stats.biggest_win THEN $3
|
|
176
|
+
ELSE player_stats.biggest_win_round
|
|
177
|
+
END,
|
|
178
|
+
current_win_streak = $4,
|
|
179
|
+
longest_win_streak = GREATEST(COALESCE(player_stats.longest_win_streak, 0), $4),
|
|
180
|
+
last_win_round = $3,
|
|
181
|
+
last_played = NOW()
|
|
182
|
+
`, [walletAddress, winSOL, roundIdNum, newStreak]);
|
|
183
|
+
|
|
184
|
+
// Get current cumulative PNL
|
|
185
|
+
const result = await this.pool.query(
|
|
186
|
+
'SELECT net_pnl FROM player_stats WHERE wallet_address = $1',
|
|
187
|
+
[walletAddress]
|
|
188
|
+
);
|
|
189
|
+
const cumulativePNL = parseFloat(result.rows[0].net_pnl);
|
|
190
|
+
|
|
191
|
+
// Add to history for chart
|
|
192
|
+
await this.pool.query(`
|
|
193
|
+
INSERT INTO player_history (
|
|
194
|
+
wallet_address,
|
|
195
|
+
round_id,
|
|
196
|
+
action,
|
|
197
|
+
amount,
|
|
198
|
+
cumulative_pnl
|
|
199
|
+
)
|
|
200
|
+
VALUES ($1, $2, 'win', $3, $4)
|
|
201
|
+
`, [walletAddress, roundIdNum, winSOL, cumulativePNL]);
|
|
202
|
+
|
|
203
|
+
console.log(`🏆 Win recorded: ${walletAddress.slice(0, 8)}... won ◎${winSOL} (streak: ${newStreak}🔥)`);
|
|
204
|
+
|
|
205
|
+
return { newStreak };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error('Error recording win:', error);
|
|
208
|
+
throw error;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get player stats
|
|
214
|
+
*/
|
|
215
|
+
async getPlayerStats(walletAddress) {
|
|
216
|
+
try {
|
|
217
|
+
const result = await this.pool.query(
|
|
218
|
+
'SELECT * FROM player_stats WHERE wallet_address = $1',
|
|
219
|
+
[walletAddress]
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (result.rows.length === 0) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const stats = result.rows[0];
|
|
227
|
+
return {
|
|
228
|
+
walletAddress: stats.wallet_address,
|
|
229
|
+
totalWagered: parseFloat(stats.total_wagered),
|
|
230
|
+
totalWon: parseFloat(stats.total_won),
|
|
231
|
+
netPNL: parseFloat(stats.net_pnl),
|
|
232
|
+
roundsPlayed: stats.rounds_played,
|
|
233
|
+
roundsWon: stats.rounds_won,
|
|
234
|
+
winRate: stats.rounds_played > 0 ? (stats.rounds_won / stats.rounds_played * 100).toFixed(2) : 0,
|
|
235
|
+
biggestWin: parseFloat(stats.biggest_win),
|
|
236
|
+
biggestWinRound: stats.biggest_win_round,
|
|
237
|
+
currentWinStreak: stats.current_win_streak || 0,
|
|
238
|
+
longestWinStreak: stats.longest_win_streak || 0,
|
|
239
|
+
lastWinRound: stats.last_win_round,
|
|
240
|
+
lastPlayed: stats.last_played,
|
|
241
|
+
createdAt: stats.created_at,
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error('Error getting player stats:', error);
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get the luckiest player (highest current winning streak)
|
|
251
|
+
*/
|
|
252
|
+
async getLuckiestPlayer() {
|
|
253
|
+
try {
|
|
254
|
+
const result = await this.pool.query(`
|
|
255
|
+
SELECT
|
|
256
|
+
wallet_address,
|
|
257
|
+
current_win_streak,
|
|
258
|
+
longest_win_streak,
|
|
259
|
+
rounds_won,
|
|
260
|
+
total_won,
|
|
261
|
+
last_win_round
|
|
262
|
+
FROM player_stats
|
|
263
|
+
WHERE current_win_streak > 0
|
|
264
|
+
ORDER BY current_win_streak DESC, total_won DESC
|
|
265
|
+
LIMIT 1
|
|
266
|
+
`);
|
|
267
|
+
|
|
268
|
+
if (result.rows.length === 0) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const stats = result.rows[0];
|
|
273
|
+
return {
|
|
274
|
+
walletAddress: stats.wallet_address,
|
|
275
|
+
currentStreak: stats.current_win_streak,
|
|
276
|
+
longestStreak: stats.longest_win_streak,
|
|
277
|
+
roundsWon: stats.rounds_won,
|
|
278
|
+
totalWon: parseFloat(stats.total_won),
|
|
279
|
+
lastWinRound: stats.last_win_round,
|
|
280
|
+
};
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.error('Error getting luckiest player:', error);
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get top streakers (leaderboard by current streak)
|
|
289
|
+
*/
|
|
290
|
+
async getStreakLeaderboard(limit = 10) {
|
|
291
|
+
try {
|
|
292
|
+
const result = await this.pool.query(`
|
|
293
|
+
SELECT
|
|
294
|
+
wallet_address,
|
|
295
|
+
current_win_streak,
|
|
296
|
+
longest_win_streak,
|
|
297
|
+
rounds_won,
|
|
298
|
+
total_won
|
|
299
|
+
FROM player_stats
|
|
300
|
+
WHERE current_win_streak > 0
|
|
301
|
+
ORDER BY current_win_streak DESC, total_won DESC
|
|
302
|
+
LIMIT $1
|
|
303
|
+
`, [limit]);
|
|
304
|
+
|
|
305
|
+
return result.rows.map(stats => ({
|
|
306
|
+
walletAddress: stats.wallet_address,
|
|
307
|
+
currentStreak: stats.current_win_streak,
|
|
308
|
+
longestStreak: stats.longest_win_streak,
|
|
309
|
+
roundsWon: stats.rounds_won,
|
|
310
|
+
totalWon: parseFloat(stats.total_won),
|
|
311
|
+
}));
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.error('Error getting streak leaderboard:', error);
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get player history for chart
|
|
320
|
+
*/
|
|
321
|
+
async getPlayerHistory(walletAddress, limit = 100) {
|
|
322
|
+
try {
|
|
323
|
+
const result = await this.pool.query(
|
|
324
|
+
`SELECT * FROM player_history
|
|
325
|
+
WHERE wallet_address = $1
|
|
326
|
+
ORDER BY timestamp ASC
|
|
327
|
+
LIMIT $2`,
|
|
328
|
+
[walletAddress, limit]
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return result.rows.map(row => ({
|
|
332
|
+
id: row.id,
|
|
333
|
+
roundId: row.round_id,
|
|
334
|
+
action: row.action,
|
|
335
|
+
amount: parseFloat(row.amount),
|
|
336
|
+
cumulativePNL: parseFloat(row.cumulative_pnl),
|
|
337
|
+
timestamp: row.timestamp,
|
|
338
|
+
}));
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error('Error getting player history:', error);
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get leaderboard (top players by PNL)
|
|
347
|
+
*/
|
|
348
|
+
async getLeaderboard(limit = 10) {
|
|
349
|
+
try {
|
|
350
|
+
const result = await this.pool.query(
|
|
351
|
+
`SELECT * FROM player_stats
|
|
352
|
+
ORDER BY net_pnl DESC
|
|
353
|
+
LIMIT $1`,
|
|
354
|
+
[limit]
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
return result.rows.map(stats => ({
|
|
358
|
+
walletAddress: stats.wallet_address,
|
|
359
|
+
netPNL: parseFloat(stats.net_pnl),
|
|
360
|
+
totalWagered: parseFloat(stats.total_wagered),
|
|
361
|
+
totalWon: parseFloat(stats.total_won),
|
|
362
|
+
roundsPlayed: stats.rounds_played,
|
|
363
|
+
roundsWon: stats.rounds_won,
|
|
364
|
+
winRate: stats.rounds_played > 0 ? (stats.rounds_won / stats.rounds_played * 100).toFixed(2) : 0,
|
|
365
|
+
}));
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('Error getting leaderboard:', error);
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Cleanup old history (keep last 30 days)
|
|
374
|
+
*/
|
|
375
|
+
async cleanup() {
|
|
376
|
+
try {
|
|
377
|
+
await this.pool.query(`
|
|
378
|
+
DELETE FROM player_history
|
|
379
|
+
WHERE timestamp < NOW() - INTERVAL '30 days'
|
|
380
|
+
`);
|
|
381
|
+
console.log('🧹 Cleaned up old player history (30+ days)');
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('Error cleaning up player history:', error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
module.exports = PlayerStatsService;
|
|
389
|
+
|