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,422 @@
|
|
|
1
|
+
-- ============================================
|
|
2
|
+
-- ANALYTICS TABLES MIGRATION
|
|
3
|
+
-- ============================================
|
|
4
|
+
-- Run this migration to set up the unified analytics system
|
|
5
|
+
-- This expands the basic audit_logs table into a full event-driven analytics system
|
|
6
|
+
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- 1. CORE EVENTS TABLE
|
|
9
|
+
-- ============================================
|
|
10
|
+
-- This is the heart of the analytics system - every trackable action is an event
|
|
11
|
+
|
|
12
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
13
|
+
-- Primary key
|
|
14
|
+
id BIGSERIAL PRIMARY KEY,
|
|
15
|
+
|
|
16
|
+
-- Event identification
|
|
17
|
+
event_id UUID NOT NULL DEFAULT gen_random_uuid(),
|
|
18
|
+
event_name VARCHAR(100) NOT NULL,
|
|
19
|
+
event_category VARCHAR(50) NOT NULL,
|
|
20
|
+
|
|
21
|
+
-- User context (nullable for anonymous events)
|
|
22
|
+
user_id VARCHAR(255), -- wallet_address or user identifier
|
|
23
|
+
session_id VARCHAR(100), -- Browser session ID
|
|
24
|
+
device_id VARCHAR(100), -- Device fingerprint (optional)
|
|
25
|
+
|
|
26
|
+
-- Event data
|
|
27
|
+
properties JSONB DEFAULT '{}', -- Event-specific data
|
|
28
|
+
context JSONB DEFAULT '{}', -- Environmental context (device, page, etc.)
|
|
29
|
+
|
|
30
|
+
-- Timing
|
|
31
|
+
client_timestamp TIMESTAMPTZ, -- When event occurred on client
|
|
32
|
+
server_timestamp TIMESTAMPTZ DEFAULT NOW(),
|
|
33
|
+
|
|
34
|
+
-- Source tracking
|
|
35
|
+
source VARCHAR(50) DEFAULT 'web', -- web, mobile, telegram, api, server
|
|
36
|
+
version VARCHAR(20), -- App version
|
|
37
|
+
|
|
38
|
+
-- Indexing helpers (denormalized for performance)
|
|
39
|
+
date_partition DATE GENERATED ALWAYS AS (DATE(server_timestamp)) STORED,
|
|
40
|
+
hour_partition INTEGER GENERATED ALWAYS AS (EXTRACT(HOUR FROM server_timestamp)::INTEGER) STORED,
|
|
41
|
+
|
|
42
|
+
-- For funnel analysis
|
|
43
|
+
funnel_step VARCHAR(100),
|
|
44
|
+
funnel_id VARCHAR(100)
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
-- Add comment
|
|
48
|
+
COMMENT ON TABLE events IS 'Unified event stream for analytics, logging, and auditing';
|
|
49
|
+
|
|
50
|
+
-- Performance indexes
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_events_user_time ON events(user_id, server_timestamp DESC);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_events_category_time ON events(event_category, server_timestamp DESC);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_events_name_time ON events(event_name, server_timestamp DESC);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id, server_timestamp);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_events_date_partition ON events(date_partition);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_events_funnel ON events(funnel_id, funnel_step) WHERE funnel_id IS NOT NULL;
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_events_properties ON events USING gin(properties);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_events_context ON events USING gin(context);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_events_source ON events(source, server_timestamp DESC);
|
|
60
|
+
|
|
61
|
+
-- ============================================
|
|
62
|
+
-- 2. SESSION SUMMARIES TABLE
|
|
63
|
+
-- ============================================
|
|
64
|
+
-- Aggregated session data for faster querying
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS session_summaries (
|
|
67
|
+
id SERIAL PRIMARY KEY,
|
|
68
|
+
session_id VARCHAR(100) UNIQUE NOT NULL,
|
|
69
|
+
user_id VARCHAR(255),
|
|
70
|
+
|
|
71
|
+
-- Session timing
|
|
72
|
+
started_at TIMESTAMPTZ NOT NULL,
|
|
73
|
+
ended_at TIMESTAMPTZ,
|
|
74
|
+
last_activity_at TIMESTAMPTZ,
|
|
75
|
+
duration_seconds INTEGER,
|
|
76
|
+
|
|
77
|
+
-- Session metrics
|
|
78
|
+
event_count INTEGER DEFAULT 0,
|
|
79
|
+
page_views INTEGER DEFAULT 0,
|
|
80
|
+
games_viewed INTEGER DEFAULT 0,
|
|
81
|
+
games_created INTEGER DEFAULT 0,
|
|
82
|
+
games_joined INTEGER DEFAULT 0,
|
|
83
|
+
messages_sent INTEGER DEFAULT 0,
|
|
84
|
+
|
|
85
|
+
-- Conversion tracking
|
|
86
|
+
converted BOOLEAN DEFAULT FALSE,
|
|
87
|
+
conversion_type VARCHAR(50),
|
|
88
|
+
conversion_value NUMERIC(20, 9),
|
|
89
|
+
|
|
90
|
+
-- Device/context
|
|
91
|
+
device_type VARCHAR(50), -- mobile, tablet, desktop
|
|
92
|
+
platform VARCHAR(50), -- web, telegram, ios, android
|
|
93
|
+
browser VARCHAR(100),
|
|
94
|
+
os VARCHAR(100),
|
|
95
|
+
screen_resolution VARCHAR(20),
|
|
96
|
+
|
|
97
|
+
-- Traffic source
|
|
98
|
+
referrer VARCHAR(500),
|
|
99
|
+
landing_page VARCHAR(500),
|
|
100
|
+
utm_source VARCHAR(100),
|
|
101
|
+
utm_medium VARCHAR(100),
|
|
102
|
+
utm_campaign VARCHAR(100),
|
|
103
|
+
utm_term VARCHAR(100),
|
|
104
|
+
utm_content VARCHAR(100),
|
|
105
|
+
|
|
106
|
+
-- Geographic (optional - from IP lookup)
|
|
107
|
+
country_code VARCHAR(5),
|
|
108
|
+
region VARCHAR(100),
|
|
109
|
+
city VARCHAR(100),
|
|
110
|
+
|
|
111
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
112
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
COMMENT ON TABLE session_summaries IS 'Aggregated session data for analytics dashboards';
|
|
116
|
+
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_session_user ON session_summaries(user_id);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_session_time ON session_summaries(started_at DESC);
|
|
119
|
+
CREATE INDEX IF NOT EXISTS idx_session_converted ON session_summaries(converted, conversion_type);
|
|
120
|
+
CREATE INDEX IF NOT EXISTS idx_session_platform ON session_summaries(platform, started_at DESC);
|
|
121
|
+
|
|
122
|
+
-- ============================================
|
|
123
|
+
-- 3. DAILY METRICS TABLE
|
|
124
|
+
-- ============================================
|
|
125
|
+
-- Pre-aggregated daily metrics for fast dashboard queries
|
|
126
|
+
|
|
127
|
+
CREATE TABLE IF NOT EXISTS daily_metrics (
|
|
128
|
+
id SERIAL PRIMARY KEY,
|
|
129
|
+
metric_date DATE NOT NULL,
|
|
130
|
+
metric_name VARCHAR(100) NOT NULL,
|
|
131
|
+
|
|
132
|
+
-- Dimensions (nullable - allows various groupings)
|
|
133
|
+
dimension_1 VARCHAR(100), -- e.g., sport type, platform, country
|
|
134
|
+
dimension_2 VARCHAR(100), -- e.g., device type, game mode
|
|
135
|
+
dimension_3 VARCHAR(100), -- e.g., utm_source
|
|
136
|
+
|
|
137
|
+
-- Metrics
|
|
138
|
+
count BIGINT DEFAULT 0,
|
|
139
|
+
sum_value NUMERIC(20, 9) DEFAULT 0,
|
|
140
|
+
avg_value NUMERIC(20, 9),
|
|
141
|
+
min_value NUMERIC(20, 9),
|
|
142
|
+
max_value NUMERIC(20, 9),
|
|
143
|
+
unique_users INTEGER DEFAULT 0,
|
|
144
|
+
unique_sessions INTEGER DEFAULT 0,
|
|
145
|
+
|
|
146
|
+
-- Metadata
|
|
147
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
148
|
+
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
149
|
+
|
|
150
|
+
UNIQUE(metric_date, metric_name, dimension_1, dimension_2, dimension_3)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
COMMENT ON TABLE daily_metrics IS 'Pre-aggregated metrics for dashboard performance';
|
|
154
|
+
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_daily_metrics_date ON daily_metrics(metric_date DESC);
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_daily_metrics_name ON daily_metrics(metric_name, metric_date DESC);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_daily_metrics_dimensions ON daily_metrics(metric_name, dimension_1, dimension_2);
|
|
158
|
+
|
|
159
|
+
-- ============================================
|
|
160
|
+
-- 4. FUNNEL DEFINITIONS TABLE
|
|
161
|
+
-- ============================================
|
|
162
|
+
-- Store funnel configurations for analysis
|
|
163
|
+
|
|
164
|
+
CREATE TABLE IF NOT EXISTS funnel_definitions (
|
|
165
|
+
id SERIAL PRIMARY KEY,
|
|
166
|
+
funnel_id VARCHAR(100) UNIQUE NOT NULL,
|
|
167
|
+
funnel_name VARCHAR(200) NOT NULL,
|
|
168
|
+
description TEXT,
|
|
169
|
+
|
|
170
|
+
-- Steps as ordered JSON array
|
|
171
|
+
-- [{"step": 1, "event_name": "wallet_connected", "label": "Connected Wallet"}, ...]
|
|
172
|
+
steps JSONB NOT NULL,
|
|
173
|
+
|
|
174
|
+
-- Configuration
|
|
175
|
+
window_hours INTEGER DEFAULT 24, -- Max time between first and last step
|
|
176
|
+
require_order BOOLEAN DEFAULT TRUE, -- Must steps happen in order?
|
|
177
|
+
is_active BOOLEAN DEFAULT TRUE,
|
|
178
|
+
|
|
179
|
+
-- Ownership
|
|
180
|
+
created_by VARCHAR(255),
|
|
181
|
+
|
|
182
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
183
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
COMMENT ON TABLE funnel_definitions IS 'Funnel configurations for conversion analysis';
|
|
187
|
+
|
|
188
|
+
-- ============================================
|
|
189
|
+
-- 5. USER ANALYTICS PROFILES
|
|
190
|
+
-- ============================================
|
|
191
|
+
-- Aggregated user-level analytics (updated periodically)
|
|
192
|
+
|
|
193
|
+
CREATE TABLE IF NOT EXISTS user_analytics (
|
|
194
|
+
id SERIAL PRIMARY KEY,
|
|
195
|
+
user_id VARCHAR(255) UNIQUE NOT NULL,
|
|
196
|
+
|
|
197
|
+
-- Lifetime metrics
|
|
198
|
+
first_seen_at TIMESTAMPTZ,
|
|
199
|
+
last_seen_at TIMESTAMPTZ,
|
|
200
|
+
total_sessions INTEGER DEFAULT 0,
|
|
201
|
+
total_events INTEGER DEFAULT 0,
|
|
202
|
+
|
|
203
|
+
-- Engagement
|
|
204
|
+
total_page_views INTEGER DEFAULT 0,
|
|
205
|
+
total_games_created INTEGER DEFAULT 0,
|
|
206
|
+
total_games_joined INTEGER DEFAULT 0,
|
|
207
|
+
total_games_won INTEGER DEFAULT 0,
|
|
208
|
+
total_messages_sent INTEGER DEFAULT 0,
|
|
209
|
+
|
|
210
|
+
-- Financial
|
|
211
|
+
total_wagered_sol NUMERIC(20, 9) DEFAULT 0,
|
|
212
|
+
total_won_sol NUMERIC(20, 9) DEFAULT 0,
|
|
213
|
+
total_claimed_sol NUMERIC(20, 9) DEFAULT 0,
|
|
214
|
+
|
|
215
|
+
-- Referral metrics
|
|
216
|
+
referral_code VARCHAR(50),
|
|
217
|
+
referred_by VARCHAR(255),
|
|
218
|
+
total_referrals INTEGER DEFAULT 0,
|
|
219
|
+
|
|
220
|
+
-- Segmentation
|
|
221
|
+
is_paying_user BOOLEAN DEFAULT FALSE,
|
|
222
|
+
first_wager_at TIMESTAMPTZ,
|
|
223
|
+
user_tier VARCHAR(50) DEFAULT 'new', -- new, active, power, whale
|
|
224
|
+
|
|
225
|
+
-- Churn prediction
|
|
226
|
+
days_since_last_activity INTEGER,
|
|
227
|
+
churn_risk VARCHAR(20), -- low, medium, high
|
|
228
|
+
|
|
229
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
230
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
COMMENT ON TABLE user_analytics IS 'Aggregated user-level analytics for segmentation and analysis';
|
|
234
|
+
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_user_analytics_tier ON user_analytics(user_tier);
|
|
236
|
+
CREATE INDEX IF NOT EXISTS idx_user_analytics_churn ON user_analytics(churn_risk);
|
|
237
|
+
CREATE INDEX IF NOT EXISTS idx_user_analytics_last_seen ON user_analytics(last_seen_at DESC);
|
|
238
|
+
|
|
239
|
+
-- ============================================
|
|
240
|
+
-- 6. INSERT DEFAULT FUNNELS
|
|
241
|
+
-- ============================================
|
|
242
|
+
|
|
243
|
+
INSERT INTO funnel_definitions (funnel_id, funnel_name, description, steps, window_hours)
|
|
244
|
+
VALUES
|
|
245
|
+
(
|
|
246
|
+
'registration',
|
|
247
|
+
'User Registration',
|
|
248
|
+
'Track users from wallet connection to completed registration',
|
|
249
|
+
'[
|
|
250
|
+
{"step": 1, "event_name": "wallet_connected", "label": "Connected Wallet"},
|
|
251
|
+
{"step": 2, "event_name": "registration_started", "label": "Started Registration"},
|
|
252
|
+
{"step": 3, "event_name": "avatar_selected", "label": "Selected Avatar"},
|
|
253
|
+
{"step": 4, "event_name": "registration_completed", "label": "Completed Registration"}
|
|
254
|
+
]'::jsonb,
|
|
255
|
+
24
|
|
256
|
+
),
|
|
257
|
+
(
|
|
258
|
+
'first_bet',
|
|
259
|
+
'First Bet',
|
|
260
|
+
'Track users from viewing games to placing their first bet',
|
|
261
|
+
'[
|
|
262
|
+
{"step": 1, "event_name": "game_viewed", "label": "Viewed Game"},
|
|
263
|
+
{"step": 2, "event_name": "game_create_started", "label": "Started Creating Game"},
|
|
264
|
+
{"step": 3, "event_name": "team_selected", "label": "Selected Team"},
|
|
265
|
+
{"step": 4, "event_name": "wager_amount_set", "label": "Set Wager Amount"},
|
|
266
|
+
{"step": 5, "event_name": "transaction_signed", "label": "Signed Transaction"},
|
|
267
|
+
{"step": 6, "event_name": "game_created", "label": "Game Created"}
|
|
268
|
+
]'::jsonb,
|
|
269
|
+
2
|
|
270
|
+
),
|
|
271
|
+
(
|
|
272
|
+
'sports_betting',
|
|
273
|
+
'Sports Betting Flow',
|
|
274
|
+
'Track the sports betting user journey',
|
|
275
|
+
'[
|
|
276
|
+
{"step": 1, "event_name": "page_viewed", "label": "Viewed Sports Tab", "filter": {"pageName": "sports"}},
|
|
277
|
+
{"step": 2, "event_name": "game_card_clicked", "label": "Clicked Game Card"},
|
|
278
|
+
{"step": 3, "event_name": "bet_modal_opened", "label": "Opened Bet Modal"},
|
|
279
|
+
{"step": 4, "event_name": "team_selected", "label": "Selected Team"},
|
|
280
|
+
{"step": 5, "event_name": "bet_confirmed", "label": "Confirmed Bet"}
|
|
281
|
+
]'::jsonb,
|
|
282
|
+
1
|
|
283
|
+
),
|
|
284
|
+
(
|
|
285
|
+
'social_engagement',
|
|
286
|
+
'Social Engagement',
|
|
287
|
+
'Track users engaging with social features',
|
|
288
|
+
'[
|
|
289
|
+
{"step": 1, "event_name": "chat_opened", "label": "Opened Chat"},
|
|
290
|
+
{"step": 2, "event_name": "chat_message_sent", "label": "Sent Message"},
|
|
291
|
+
{"step": 3, "event_name": "friend_request_sent", "label": "Sent Friend Request"},
|
|
292
|
+
{"step": 4, "event_name": "friend_request_accepted", "label": "Friend Request Accepted"}
|
|
293
|
+
]'::jsonb,
|
|
294
|
+
168
|
|
295
|
+
),
|
|
296
|
+
(
|
|
297
|
+
'referral',
|
|
298
|
+
'Referral Flow',
|
|
299
|
+
'Track referral program engagement',
|
|
300
|
+
'[
|
|
301
|
+
{"step": 1, "event_name": "referral_code_generated", "label": "Generated Code"},
|
|
302
|
+
{"step": 2, "event_name": "referral_link_copied", "label": "Copied Link"},
|
|
303
|
+
{"step": 3, "event_name": "referral_code_used", "label": "Code Used by Friend"},
|
|
304
|
+
{"step": 4, "event_name": "referral_badge_earned", "label": "Badge Earned"}
|
|
305
|
+
]'::jsonb,
|
|
306
|
+
720
|
|
307
|
+
)
|
|
308
|
+
ON CONFLICT (funnel_id) DO NOTHING;
|
|
309
|
+
|
|
310
|
+
-- ============================================
|
|
311
|
+
-- 7. HELPER FUNCTIONS
|
|
312
|
+
-- ============================================
|
|
313
|
+
|
|
314
|
+
-- Function to update session summary on new event
|
|
315
|
+
CREATE OR REPLACE FUNCTION update_session_on_event()
|
|
316
|
+
RETURNS TRIGGER AS $$
|
|
317
|
+
BEGIN
|
|
318
|
+
INSERT INTO session_summaries (session_id, user_id, started_at, last_activity_at, event_count)
|
|
319
|
+
VALUES (NEW.session_id, NEW.user_id, NEW.server_timestamp, NEW.server_timestamp, 1)
|
|
320
|
+
ON CONFLICT (session_id) DO UPDATE SET
|
|
321
|
+
user_id = COALESCE(session_summaries.user_id, EXCLUDED.user_id),
|
|
322
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
323
|
+
event_count = session_summaries.event_count + 1,
|
|
324
|
+
page_views = session_summaries.page_views + CASE WHEN NEW.event_name = 'page_viewed' THEN 1 ELSE 0 END,
|
|
325
|
+
games_viewed = session_summaries.games_viewed + CASE WHEN NEW.event_name = 'game_viewed' THEN 1 ELSE 0 END,
|
|
326
|
+
games_created = session_summaries.games_created + CASE WHEN NEW.event_name = 'game_created' THEN 1 ELSE 0 END,
|
|
327
|
+
games_joined = session_summaries.games_joined + CASE WHEN NEW.event_name = 'game_joined' THEN 1 ELSE 0 END,
|
|
328
|
+
messages_sent = session_summaries.messages_sent + CASE WHEN NEW.event_name = 'chat_message_sent' THEN 1 ELSE 0 END,
|
|
329
|
+
updated_at = NOW();
|
|
330
|
+
|
|
331
|
+
RETURN NEW;
|
|
332
|
+
END;
|
|
333
|
+
$$ LANGUAGE plpgsql;
|
|
334
|
+
|
|
335
|
+
-- Create trigger (only if session_id is provided)
|
|
336
|
+
DROP TRIGGER IF EXISTS trigger_update_session ON events;
|
|
337
|
+
CREATE TRIGGER trigger_update_session
|
|
338
|
+
AFTER INSERT ON events
|
|
339
|
+
FOR EACH ROW
|
|
340
|
+
WHEN (NEW.session_id IS NOT NULL)
|
|
341
|
+
EXECUTE FUNCTION update_session_on_event();
|
|
342
|
+
|
|
343
|
+
-- Function to update user analytics on event
|
|
344
|
+
CREATE OR REPLACE FUNCTION update_user_analytics_on_event()
|
|
345
|
+
RETURNS TRIGGER AS $$
|
|
346
|
+
BEGIN
|
|
347
|
+
INSERT INTO user_analytics (user_id, first_seen_at, last_seen_at, total_events)
|
|
348
|
+
VALUES (NEW.user_id, NEW.server_timestamp, NEW.server_timestamp, 1)
|
|
349
|
+
ON CONFLICT (user_id) DO UPDATE SET
|
|
350
|
+
last_seen_at = EXCLUDED.last_seen_at,
|
|
351
|
+
total_events = user_analytics.total_events + 1,
|
|
352
|
+
total_page_views = user_analytics.total_page_views + CASE WHEN NEW.event_name = 'page_viewed' THEN 1 ELSE 0 END,
|
|
353
|
+
total_games_created = user_analytics.total_games_created + CASE WHEN NEW.event_name = 'game_created' THEN 1 ELSE 0 END,
|
|
354
|
+
total_games_joined = user_analytics.total_games_joined + CASE WHEN NEW.event_name = 'game_joined' THEN 1 ELSE 0 END,
|
|
355
|
+
total_messages_sent = user_analytics.total_messages_sent + CASE WHEN NEW.event_name = 'chat_message_sent' THEN 1 ELSE 0 END,
|
|
356
|
+
days_since_last_activity = 0,
|
|
357
|
+
updated_at = NOW();
|
|
358
|
+
|
|
359
|
+
RETURN NEW;
|
|
360
|
+
END;
|
|
361
|
+
$$ LANGUAGE plpgsql;
|
|
362
|
+
|
|
363
|
+
-- Create trigger (only if user_id is provided)
|
|
364
|
+
DROP TRIGGER IF EXISTS trigger_update_user_analytics ON events;
|
|
365
|
+
CREATE TRIGGER trigger_update_user_analytics
|
|
366
|
+
AFTER INSERT ON events
|
|
367
|
+
FOR EACH ROW
|
|
368
|
+
WHEN (NEW.user_id IS NOT NULL)
|
|
369
|
+
EXECUTE FUNCTION update_user_analytics_on_event();
|
|
370
|
+
|
|
371
|
+
-- ============================================
|
|
372
|
+
-- 8. VIEWS FOR COMMON QUERIES
|
|
373
|
+
-- ============================================
|
|
374
|
+
|
|
375
|
+
-- Daily Active Users view
|
|
376
|
+
CREATE OR REPLACE VIEW v_daily_active_users AS
|
|
377
|
+
SELECT
|
|
378
|
+
date_partition as date,
|
|
379
|
+
COUNT(DISTINCT user_id) as dau,
|
|
380
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
381
|
+
COUNT(*) as total_events
|
|
382
|
+
FROM events
|
|
383
|
+
WHERE user_id IS NOT NULL
|
|
384
|
+
GROUP BY date_partition
|
|
385
|
+
ORDER BY date_partition DESC;
|
|
386
|
+
|
|
387
|
+
-- Events summary by category
|
|
388
|
+
CREATE OR REPLACE VIEW v_events_by_category AS
|
|
389
|
+
SELECT
|
|
390
|
+
date_partition as date,
|
|
391
|
+
event_category,
|
|
392
|
+
event_name,
|
|
393
|
+
COUNT(*) as event_count,
|
|
394
|
+
COUNT(DISTINCT user_id) as unique_users
|
|
395
|
+
FROM events
|
|
396
|
+
GROUP BY date_partition, event_category, event_name
|
|
397
|
+
ORDER BY date_partition DESC, event_count DESC;
|
|
398
|
+
|
|
399
|
+
-- Session conversion summary
|
|
400
|
+
CREATE OR REPLACE VIEW v_session_conversions AS
|
|
401
|
+
SELECT
|
|
402
|
+
DATE(started_at) as date,
|
|
403
|
+
platform,
|
|
404
|
+
COUNT(*) as total_sessions,
|
|
405
|
+
COUNT(CASE WHEN converted THEN 1 END) as converted_sessions,
|
|
406
|
+
ROUND(100.0 * COUNT(CASE WHEN converted THEN 1 END) / NULLIF(COUNT(*), 0), 2) as conversion_rate,
|
|
407
|
+
AVG(duration_seconds) as avg_session_duration
|
|
408
|
+
FROM session_summaries
|
|
409
|
+
GROUP BY DATE(started_at), platform
|
|
410
|
+
ORDER BY DATE(started_at) DESC;
|
|
411
|
+
|
|
412
|
+
-- ============================================
|
|
413
|
+
-- MIGRATION COMPLETE
|
|
414
|
+
-- ============================================
|
|
415
|
+
-- Next steps:
|
|
416
|
+
-- 1. Create TypeScript types matching this schema
|
|
417
|
+
-- 2. Build AnalyticsService on frontend
|
|
418
|
+
-- 3. Build analytics routes on backend
|
|
419
|
+
-- 4. Start tracking events!
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Migration 002: Add matchup_image_url column to games table
|
|
2
|
+
-- This column stores the S3 URL for pre-generated matchup images
|
|
3
|
+
|
|
4
|
+
ALTER TABLE games ADD COLUMN IF NOT EXISTS matchup_image_url TEXT;
|
|
5
|
+
|
|
6
|
+
-- Optional: Add an index if you'll be querying by this column
|
|
7
|
+
-- CREATE INDEX IF NOT EXISTS idx_games_matchup_image_url ON games(matchup_image_url);
|
|
8
|
+
|
|
9
|
+
-- Verify the column was added
|
|
10
|
+
SELECT column_name, data_type
|
|
11
|
+
FROM information_schema.columns
|
|
12
|
+
WHERE table_name = 'games' AND column_name = 'matchup_image_url';
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
-- REFERRAL EARNINGS SYSTEM
|
|
3
|
+
-- ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
--
|
|
5
|
+
-- This migration creates the referral earnings tracking system.
|
|
6
|
+
--
|
|
7
|
+
-- ⚠️ IMPORTANT: Commission goes to the GAME CREATOR's referrer ONLY!
|
|
8
|
+
--
|
|
9
|
+
-- The person who referred the GAME CREATOR earns 1% of the ENTIRE POT.
|
|
10
|
+
-- Other players' referrers do NOT earn anything from the game.
|
|
11
|
+
--
|
|
12
|
+
-- Fee Structure:
|
|
13
|
+
-- Current: 5% Operator + 1% Oracle = 6% Total
|
|
14
|
+
-- Proposed: 4% Operator + 1% Referrer + 1% Oracle = 6% Total
|
|
15
|
+
--
|
|
16
|
+
-- If game creator has no referrer, the 1% goes to the operator (5% total).
|
|
17
|
+
--
|
|
18
|
+
-- Example (10-player game, 0.5 SOL buy-in each = 5 SOL pot):
|
|
19
|
+
-- - Game creator was referred by UserX
|
|
20
|
+
-- - UserX earns 0.05 SOL (1% of 5 SOL pot)
|
|
21
|
+
-- - Operator gets 0.20 SOL (4% of 5 SOL) instead of 0.25 SOL (5%)
|
|
22
|
+
--
|
|
23
|
+
-- ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
-- Main earnings table - tracks each individual earning event
|
|
26
|
+
CREATE TABLE IF NOT EXISTS referral_earnings (
|
|
27
|
+
id SERIAL PRIMARY KEY,
|
|
28
|
+
|
|
29
|
+
-- Relationship: Referrer (who earns) and Referee (who played)
|
|
30
|
+
referrer_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
31
|
+
referrer_wallet VARCHAR(44) NOT NULL,
|
|
32
|
+
referee_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
|
33
|
+
referee_wallet VARCHAR(44) NOT NULL,
|
|
34
|
+
|
|
35
|
+
-- Game details
|
|
36
|
+
game_id VARCHAR(255) NOT NULL,
|
|
37
|
+
game_type VARCHAR(50) NOT NULL, -- 'sports', 'billiards', 'jackpot'
|
|
38
|
+
|
|
39
|
+
-- Financial details (all in lamports)
|
|
40
|
+
pot_size BIGINT NOT NULL, -- Total pot of the game
|
|
41
|
+
referee_buy_in BIGINT NOT NULL, -- Amount referee wagered
|
|
42
|
+
referee_won BOOLEAN NOT NULL, -- Did referee win?
|
|
43
|
+
referee_payout BIGINT DEFAULT 0, -- Amount referee won (0 if lost)
|
|
44
|
+
|
|
45
|
+
-- Commission details
|
|
46
|
+
commission_rate DECIMAL(6,4) NOT NULL DEFAULT 0.0100, -- 1% = 0.0100
|
|
47
|
+
commission_amount BIGINT NOT NULL, -- Actual commission in lamports
|
|
48
|
+
|
|
49
|
+
-- Status and payout tracking
|
|
50
|
+
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'cancelled')),
|
|
51
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
52
|
+
paid_at TIMESTAMP,
|
|
53
|
+
payout_tx_signature VARCHAR(128),
|
|
54
|
+
payout_batch_id INTEGER,
|
|
55
|
+
notes TEXT, -- Optional notes (e.g., "Paid on-chain during game resolution")
|
|
56
|
+
|
|
57
|
+
-- Prevent duplicate entries for same referee in same game
|
|
58
|
+
UNIQUE(referee_wallet, game_id)
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
-- Indexes for efficient queries
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_referrer ON referral_earnings(referrer_user_id, status);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_referrer_wallet ON referral_earnings(referrer_wallet);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_referee ON referral_earnings(referee_user_id);
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_game ON referral_earnings(game_id);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_status ON referral_earnings(status);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_created ON referral_earnings(created_at DESC);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_referral_earnings_pending ON referral_earnings(referrer_wallet) WHERE status = 'pending';
|
|
69
|
+
|
|
70
|
+
-- Payout batches table - tracks batch payouts to referrers
|
|
71
|
+
CREATE TABLE IF NOT EXISTS referral_payout_batches (
|
|
72
|
+
id SERIAL PRIMARY KEY,
|
|
73
|
+
|
|
74
|
+
-- Batch details
|
|
75
|
+
total_amount BIGINT NOT NULL, -- Total lamports in this batch
|
|
76
|
+
num_referrers INTEGER NOT NULL, -- Number of unique referrers paid
|
|
77
|
+
num_earnings INTEGER NOT NULL, -- Number of individual earning records
|
|
78
|
+
|
|
79
|
+
-- Transaction details
|
|
80
|
+
tx_signature VARCHAR(128),
|
|
81
|
+
status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
|
82
|
+
|
|
83
|
+
-- Timing
|
|
84
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
85
|
+
processed_at TIMESTAMP,
|
|
86
|
+
completed_at TIMESTAMP,
|
|
87
|
+
|
|
88
|
+
-- Error tracking
|
|
89
|
+
error_message TEXT,
|
|
90
|
+
retry_count INTEGER DEFAULT 0
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_payout_batches_status ON referral_payout_batches(status);
|
|
94
|
+
|
|
95
|
+
-- Aggregated summary view for quick dashboard stats
|
|
96
|
+
CREATE OR REPLACE VIEW referral_earnings_summary AS
|
|
97
|
+
SELECT
|
|
98
|
+
u.id as user_id,
|
|
99
|
+
u.wallet_address,
|
|
100
|
+
u.username,
|
|
101
|
+
u.my_referral_code,
|
|
102
|
+
COALESCE(COUNT(re.id), 0) as total_referral_games,
|
|
103
|
+
COALESCE(SUM(re.commission_amount), 0) as total_earned_lamports,
|
|
104
|
+
COALESCE(SUM(CASE WHEN re.status = 'pending' THEN re.commission_amount ELSE 0 END), 0) as pending_lamports,
|
|
105
|
+
COALESCE(SUM(CASE WHEN re.status = 'paid' THEN re.commission_amount ELSE 0 END), 0) as paid_lamports,
|
|
106
|
+
MAX(re.created_at) as last_earning_at
|
|
107
|
+
FROM users u
|
|
108
|
+
LEFT JOIN referral_earnings re ON u.wallet_address = re.referrer_wallet
|
|
109
|
+
WHERE u.my_referral_code IS NOT NULL
|
|
110
|
+
GROUP BY u.id, u.wallet_address, u.username, u.my_referral_code;
|
|
111
|
+
|
|
112
|
+
-- Function to calculate referral commission for a game
|
|
113
|
+
-- Call this after game resolution to record earnings for all players with referrers
|
|
114
|
+
CREATE OR REPLACE FUNCTION process_game_referral_commissions(
|
|
115
|
+
p_game_id VARCHAR(255),
|
|
116
|
+
p_game_type VARCHAR(50),
|
|
117
|
+
p_pot_size BIGINT,
|
|
118
|
+
p_commission_rate DECIMAL DEFAULT 0.0100
|
|
119
|
+
)
|
|
120
|
+
RETURNS INTEGER AS $$
|
|
121
|
+
DECLARE
|
|
122
|
+
v_count INTEGER := 0;
|
|
123
|
+
v_player RECORD;
|
|
124
|
+
BEGIN
|
|
125
|
+
-- For each player in the game who has a referrer
|
|
126
|
+
FOR v_player IN
|
|
127
|
+
SELECT
|
|
128
|
+
ugr.wallet_address as referee_wallet,
|
|
129
|
+
ugr.buy_in,
|
|
130
|
+
ugr.won_game,
|
|
131
|
+
ugr.amount_claimed,
|
|
132
|
+
u.id as referee_user_id,
|
|
133
|
+
u.referral_code,
|
|
134
|
+
referrer.id as referrer_user_id,
|
|
135
|
+
referrer.wallet_address as referrer_wallet
|
|
136
|
+
FROM user_game_refs ugr
|
|
137
|
+
JOIN users u ON ugr.wallet_address = u.wallet_address
|
|
138
|
+
JOIN users referrer ON u.referral_code = referrer.my_referral_code
|
|
139
|
+
WHERE ugr.game_id = p_game_id
|
|
140
|
+
AND u.referral_code IS NOT NULL
|
|
141
|
+
AND referrer.my_referral_code IS NOT NULL
|
|
142
|
+
LOOP
|
|
143
|
+
-- Calculate commission (1% of pot size, proportional to player's contribution)
|
|
144
|
+
-- Each player's referrer gets 1% of THAT PLAYER's buy-in
|
|
145
|
+
DECLARE
|
|
146
|
+
v_commission BIGINT;
|
|
147
|
+
BEGIN
|
|
148
|
+
-- Commission = 1% of referee's buy-in (in lamports)
|
|
149
|
+
v_commission := FLOOR(v_player.buy_in * 1000000000 * p_commission_rate);
|
|
150
|
+
|
|
151
|
+
-- Skip if commission is 0
|
|
152
|
+
IF v_commission > 0 THEN
|
|
153
|
+
-- Insert or update earning record
|
|
154
|
+
INSERT INTO referral_earnings (
|
|
155
|
+
referrer_user_id,
|
|
156
|
+
referrer_wallet,
|
|
157
|
+
referee_user_id,
|
|
158
|
+
referee_wallet,
|
|
159
|
+
game_id,
|
|
160
|
+
game_type,
|
|
161
|
+
pot_size,
|
|
162
|
+
referee_buy_in,
|
|
163
|
+
referee_won,
|
|
164
|
+
referee_payout,
|
|
165
|
+
commission_rate,
|
|
166
|
+
commission_amount,
|
|
167
|
+
status
|
|
168
|
+
) VALUES (
|
|
169
|
+
v_player.referrer_user_id,
|
|
170
|
+
v_player.referrer_wallet,
|
|
171
|
+
v_player.referee_user_id,
|
|
172
|
+
v_player.referee_wallet,
|
|
173
|
+
p_game_id,
|
|
174
|
+
p_game_type,
|
|
175
|
+
p_pot_size,
|
|
176
|
+
FLOOR(v_player.buy_in * 1000000000), -- Convert SOL to lamports
|
|
177
|
+
COALESCE(v_player.won_game, false),
|
|
178
|
+
COALESCE(FLOOR(v_player.amount_claimed * 1000000000), 0),
|
|
179
|
+
p_commission_rate,
|
|
180
|
+
v_commission,
|
|
181
|
+
'pending'
|
|
182
|
+
)
|
|
183
|
+
ON CONFLICT (referee_wallet, game_id) DO NOTHING;
|
|
184
|
+
|
|
185
|
+
v_count := v_count + 1;
|
|
186
|
+
END IF;
|
|
187
|
+
END;
|
|
188
|
+
END LOOP;
|
|
189
|
+
|
|
190
|
+
RETURN v_count;
|
|
191
|
+
END;
|
|
192
|
+
$$ LANGUAGE plpgsql;
|
|
193
|
+
|
|
194
|
+
-- Grant necessary permissions (if using different roles)
|
|
195
|
+
-- GRANT ALL ON referral_earnings TO your_app_role;
|
|
196
|
+
-- GRANT ALL ON referral_payout_batches TO your_app_role;
|
|
197
|
+
-- GRANT SELECT ON referral_earnings_summary TO your_app_role;
|
|
198
|
+
|
|
199
|
+
-- Log migration
|
|
200
|
+
DO $$
|
|
201
|
+
BEGIN
|
|
202
|
+
RAISE NOTICE '✅ Referral earnings tables created successfully';
|
|
203
|
+
RAISE NOTICE ' - referral_earnings: tracks individual commission events';
|
|
204
|
+
RAISE NOTICE ' - referral_payout_batches: tracks batch payouts';
|
|
205
|
+
RAISE NOTICE ' - referral_earnings_summary: aggregated view for dashboards';
|
|
206
|
+
RAISE NOTICE ' - process_game_referral_commissions(): function to process game commissions';
|
|
207
|
+
END $$;
|
|
208
|
+
|