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,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* š Check Current Chat Database Schema
|
|
3
|
+
*
|
|
4
|
+
* Inspects the actual Heroku Postgres database to see what we have
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Pool } = require('pg');
|
|
8
|
+
|
|
9
|
+
async function checkSchema() {
|
|
10
|
+
const pool = new Pool({
|
|
11
|
+
connectionString: process.env.DATABASE_URL,
|
|
12
|
+
ssl: process.env.DATABASE_URL ? { rejectUnauthorized: false } : false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
console.log('š Checking Heroku Postgres database...\n');
|
|
17
|
+
|
|
18
|
+
// Check if table exists
|
|
19
|
+
const tableCheck = await pool.query(`
|
|
20
|
+
SELECT EXISTS (
|
|
21
|
+
SELECT FROM information_schema.tables
|
|
22
|
+
WHERE table_name = 'chat_messages'
|
|
23
|
+
);
|
|
24
|
+
`);
|
|
25
|
+
|
|
26
|
+
if (!tableCheck.rows[0].exists) {
|
|
27
|
+
console.log('ā Table chat_messages does NOT exist!');
|
|
28
|
+
await pool.end();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log('ā
Table chat_messages EXISTS\n');
|
|
33
|
+
|
|
34
|
+
// Get all columns
|
|
35
|
+
const columns = await pool.query(`
|
|
36
|
+
SELECT
|
|
37
|
+
column_name,
|
|
38
|
+
data_type,
|
|
39
|
+
is_nullable,
|
|
40
|
+
column_default
|
|
41
|
+
FROM information_schema.columns
|
|
42
|
+
WHERE table_name = 'chat_messages'
|
|
43
|
+
ORDER BY ordinal_position;
|
|
44
|
+
`);
|
|
45
|
+
|
|
46
|
+
console.log('š CURRENT TABLE STRUCTURE:');
|
|
47
|
+
console.log('ā'.repeat(80));
|
|
48
|
+
columns.rows.forEach(row => {
|
|
49
|
+
console.log(` ${row.column_name.padEnd(30)} ${row.data_type.padEnd(20)} ${row.is_nullable === 'YES' ? 'NULL' : 'NOT NULL'}`);
|
|
50
|
+
if (row.column_default) {
|
|
51
|
+
console.log(` Default: ${row.column_default}`);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
console.log('ā'.repeat(80));
|
|
55
|
+
|
|
56
|
+
// Get indexes
|
|
57
|
+
const indexes = await pool.query(`
|
|
58
|
+
SELECT indexname, indexdef
|
|
59
|
+
FROM pg_indexes
|
|
60
|
+
WHERE tablename = 'chat_messages';
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
if (indexes.rows.length > 0) {
|
|
64
|
+
console.log('\nš INDEXES:');
|
|
65
|
+
indexes.rows.forEach(row => {
|
|
66
|
+
console.log(` ${row.indexname}`);
|
|
67
|
+
console.log(` ${row.indexdef}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get row count
|
|
72
|
+
const count = await pool.query(`SELECT COUNT(*) FROM chat_messages;`);
|
|
73
|
+
console.log(`\nš¬ Total messages in database: ${count.rows[0].count}`);
|
|
74
|
+
|
|
75
|
+
// Get sample messages
|
|
76
|
+
const sample = await pool.query(`
|
|
77
|
+
SELECT * FROM chat_messages
|
|
78
|
+
ORDER BY timestamp DESC
|
|
79
|
+
LIMIT 3;
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
if (sample.rows.length > 0) {
|
|
83
|
+
console.log('\nš SAMPLE MESSAGES (most recent):');
|
|
84
|
+
sample.rows.forEach((msg, i) => {
|
|
85
|
+
console.log(`\n Message ${i + 1}:`);
|
|
86
|
+
console.log(` ID: ${msg.id}`);
|
|
87
|
+
console.log(` Wallet: ${msg.wallet_address?.slice(0, 8)}...`);
|
|
88
|
+
console.log(` Message: ${msg.message?.slice(0, 50)}...`);
|
|
89
|
+
console.log(` Time: ${msg.timestamp}`);
|
|
90
|
+
// Check for new columns
|
|
91
|
+
if ('is_winner_announcement' in msg) {
|
|
92
|
+
console.log(` š Winner: ${msg.is_winner_announcement}`);
|
|
93
|
+
}
|
|
94
|
+
if ('win_amount' in msg) {
|
|
95
|
+
console.log(` š° Amount: ${msg.win_amount}`);
|
|
96
|
+
}
|
|
97
|
+
if ('round_id' in msg) {
|
|
98
|
+
console.log(` š° Round: ${msg.round_id}`);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check what columns are MISSING for winner announcements
|
|
104
|
+
console.log('\nš„ WINNER ANNOUNCEMENT FEATURE CHECK:');
|
|
105
|
+
const hasWinnerCol = columns.rows.some(c => c.column_name === 'is_winner_announcement');
|
|
106
|
+
const hasAmountCol = columns.rows.some(c => c.column_name === 'win_amount');
|
|
107
|
+
const hasRoundCol = columns.rows.some(c => c.column_name === 'round_id');
|
|
108
|
+
|
|
109
|
+
console.log(` is_winner_announcement: ${hasWinnerCol ? 'ā
EXISTS' : 'ā MISSING'}`);
|
|
110
|
+
console.log(` win_amount: ${hasAmountCol ? 'ā
EXISTS' : 'ā MISSING'}`);
|
|
111
|
+
console.log(` round_id: ${hasRoundCol ? 'ā
EXISTS' : 'ā MISSING'}`);
|
|
112
|
+
|
|
113
|
+
if (!hasWinnerCol || !hasAmountCol || !hasRoundCol) {
|
|
114
|
+
console.log('\nā ļø MIGRATION NEEDED! Run: node scripts/migrate-chat-winners.js');
|
|
115
|
+
} else {
|
|
116
|
+
console.log('\nā
All columns exist! Winner announcements should work!');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await pool.end();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('ā Error:', error.message);
|
|
122
|
+
console.error(error);
|
|
123
|
+
await pool.end();
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Run it
|
|
129
|
+
checkSchema();
|
|
130
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# š Check Chat Database Schema on Heroku
|
|
4
|
+
# This runs the check script on Heroku directly
|
|
5
|
+
|
|
6
|
+
echo "š Checking database schema on Heroku..."
|
|
7
|
+
echo ""
|
|
8
|
+
|
|
9
|
+
# Run the check script on Heroku
|
|
10
|
+
heroku run node scripts/check-chat-schema.js --app dubs-server
|
|
11
|
+
|
|
12
|
+
echo ""
|
|
13
|
+
echo "ā
Check complete!"
|
|
14
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { Connection, PublicKey } = require('@solana/web3.js');
|
|
2
|
+
const crypto = require('crypto');
|
|
3
|
+
|
|
4
|
+
const conn = new Connection('https://api.mainnet-beta.solana.com');
|
|
5
|
+
const PROGRAM_ID = new PublicKey('85wJGp9uc8w2FeKX9CEHsudTo1UVCrmuRFy37oCcaoG1');
|
|
6
|
+
|
|
7
|
+
function gameIdToU64(gameId) {
|
|
8
|
+
const hash = crypto.createHash('sha256').update(gameId).digest();
|
|
9
|
+
return hash.readBigUInt64LE(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getGamePDA(gameId) {
|
|
13
|
+
const gameIdNum = gameIdToU64(gameId);
|
|
14
|
+
const gameIdBuf = Buffer.alloc(8);
|
|
15
|
+
gameIdBuf.writeBigUInt64LE(gameIdNum);
|
|
16
|
+
const [pda] = PublicKey.findProgramAddressSync([Buffer.from('game'), gameIdBuf], PROGRAM_ID);
|
|
17
|
+
return pda;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function checkOracleInGame(gameId) {
|
|
21
|
+
const pda = getGamePDA(gameId);
|
|
22
|
+
console.log(`Game: ${gameId}`);
|
|
23
|
+
console.log(`PDA: ${pda.toString()}`);
|
|
24
|
+
|
|
25
|
+
const account = await conn.getAccountInfo(pda);
|
|
26
|
+
if (!account) {
|
|
27
|
+
console.log('ā Game account not found (might not be created yet)');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const data = account.data;
|
|
32
|
+
// Oracle field is at a specific offset in the Game struct
|
|
33
|
+
// After many fields, the oracle Pubkey is 32 bytes
|
|
34
|
+
|
|
35
|
+
// Rough parsing - oracle is near the end before claimed_players
|
|
36
|
+
// Let's look for both possible oracle addresses
|
|
37
|
+
const dataHex = data.toString('hex');
|
|
38
|
+
|
|
39
|
+
const OLD_ORACLE = 'b93a8976ea13d935dcaf96f0cb26ab727f51191d63c016724230c2c1c8bd215e'; // DU4CHEH...
|
|
40
|
+
const NEW_ORACLE = 'd790532829777790e1e36b4bcbd2516f45c480c6787a24562023caae1e0ee268bd23'; // FWUJCth...
|
|
41
|
+
|
|
42
|
+
if (dataHex.includes(OLD_ORACLE)) {
|
|
43
|
+
console.log('ā OLD ORACLE DETECTED: DU4CHEHUJ2EeezAXyfyi8vLB7dmXKq41myKnA5DZ9Mru');
|
|
44
|
+
console.log(' This game will have resolution issues!');
|
|
45
|
+
} else if (dataHex.includes(NEW_ORACLE.slice(0, 64))) {
|
|
46
|
+
console.log('ā
NEW ORACLE DETECTED: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa');
|
|
47
|
+
console.log(' This game will resolve automatically! ā
');
|
|
48
|
+
} else {
|
|
49
|
+
console.log('š Oracle address not clearly identified in data');
|
|
50
|
+
console.log(' Data length:', data.length, 'bytes');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
checkOracleInGame('sport-1767213423275-k0s504tfl').catch(console.error);
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Database Cleanup Script
|
|
5
|
+
*
|
|
6
|
+
* Manages data retention to prevent unbounded growth:
|
|
7
|
+
* - Keeps essential audit data
|
|
8
|
+
* - Archives old logs
|
|
9
|
+
* - Maintains performance
|
|
10
|
+
*
|
|
11
|
+
* Run via Heroku Scheduler: Daily at 3am
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { Pool } = require('pg');
|
|
15
|
+
|
|
16
|
+
const RETENTION_POLICIES = {
|
|
17
|
+
// Keep completed round records forever (users need to verify)
|
|
18
|
+
keeper_rounds_resolved: null, // Never delete
|
|
19
|
+
|
|
20
|
+
// Archive old incomplete/stuck rounds after 30 days
|
|
21
|
+
keeper_rounds_incomplete: 30, // days
|
|
22
|
+
|
|
23
|
+
// Keep detailed action logs for 30 days
|
|
24
|
+
keeper_actions: 30, // days
|
|
25
|
+
|
|
26
|
+
// Keep health snapshots for 90 days
|
|
27
|
+
keeper_health: 90, // days
|
|
28
|
+
|
|
29
|
+
// Keep jackpot history forever (verification data)
|
|
30
|
+
jackpot_rounds: null, // Never delete
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
class DatabaseCleaner {
|
|
34
|
+
constructor() {
|
|
35
|
+
this.pool = new Pool({
|
|
36
|
+
connectionString: process.env.DATABASE_URL,
|
|
37
|
+
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async getTableStats() {
|
|
42
|
+
const stats = {};
|
|
43
|
+
|
|
44
|
+
const tables = ['keeper_rounds', 'keeper_actions', 'keeper_health', 'jackpot_rounds'];
|
|
45
|
+
|
|
46
|
+
for (const table of tables) {
|
|
47
|
+
const result = await this.pool.query(`SELECT COUNT(*) as count FROM ${table}`);
|
|
48
|
+
const sizeResult = await this.pool.query(`
|
|
49
|
+
SELECT pg_size_pretty(pg_total_relation_size('${table}')) as size
|
|
50
|
+
`);
|
|
51
|
+
stats[table] = {
|
|
52
|
+
rows: parseInt(result.rows[0].count),
|
|
53
|
+
size: sizeResult.rows[0].size
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return stats;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async cleanupKeeperActions() {
|
|
61
|
+
console.log('\nš§¹ Cleaning keeper_actions...');
|
|
62
|
+
|
|
63
|
+
const days = RETENTION_POLICIES.keeper_actions;
|
|
64
|
+
|
|
65
|
+
const result = await this.pool.query(`
|
|
66
|
+
DELETE FROM keeper_actions
|
|
67
|
+
WHERE timestamp < NOW() - INTERVAL '${days} days'
|
|
68
|
+
RETURNING id
|
|
69
|
+
`);
|
|
70
|
+
|
|
71
|
+
console.log(` Deleted ${result.rowCount} old action logs (older than ${days} days)`);
|
|
72
|
+
return result.rowCount;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async cleanupKeeperHealth() {
|
|
76
|
+
console.log('\nš§¹ Cleaning keeper_health...');
|
|
77
|
+
|
|
78
|
+
const days = RETENTION_POLICIES.keeper_health;
|
|
79
|
+
|
|
80
|
+
const result = await this.pool.query(`
|
|
81
|
+
DELETE FROM keeper_health
|
|
82
|
+
WHERE timestamp < NOW() - INTERVAL '${days} days'
|
|
83
|
+
RETURNING id
|
|
84
|
+
`);
|
|
85
|
+
|
|
86
|
+
console.log(` Deleted ${result.rowCount} old health snapshots (older than ${days} days)`);
|
|
87
|
+
return result.rowCount;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async archiveOldIncompleteRounds() {
|
|
91
|
+
console.log('\nš§¹ Archiving incomplete keeper_rounds...');
|
|
92
|
+
|
|
93
|
+
const days = RETENTION_POLICIES.keeper_rounds_incomplete;
|
|
94
|
+
|
|
95
|
+
// Only delete rounds that are stuck/incomplete and old
|
|
96
|
+
const result = await this.pool.query(`
|
|
97
|
+
DELETE FROM keeper_rounds
|
|
98
|
+
WHERE status NOT IN ('resolved', 'open')
|
|
99
|
+
AND updated_at < NOW() - INTERVAL '${days} days'
|
|
100
|
+
RETURNING round_id, status
|
|
101
|
+
`);
|
|
102
|
+
|
|
103
|
+
console.log(` Archived ${result.rowCount} old incomplete rounds (older than ${days} days)`);
|
|
104
|
+
|
|
105
|
+
if (result.rowCount > 0) {
|
|
106
|
+
console.log(' Rounds archived:', result.rows.map(r => `${r.round_id} (${r.status})`).join(', '));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result.rowCount;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async vacuum() {
|
|
113
|
+
console.log('\nšļø Running VACUUM to reclaim space...');
|
|
114
|
+
|
|
115
|
+
await this.pool.query('VACUUM ANALYZE keeper_actions');
|
|
116
|
+
await this.pool.query('VACUUM ANALYZE keeper_health');
|
|
117
|
+
await this.pool.query('VACUUM ANALYZE keeper_rounds');
|
|
118
|
+
|
|
119
|
+
console.log(' ā
VACUUM complete');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async run() {
|
|
123
|
+
console.log('š§¹ DATABASE CLEANUP STARTING');
|
|
124
|
+
console.log('='.repeat(80));
|
|
125
|
+
console.log(`Time: ${new Date().toISOString()}`);
|
|
126
|
+
console.log();
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Show stats before cleanup
|
|
130
|
+
console.log('š Before Cleanup:');
|
|
131
|
+
const statsBefore = await this.getTableStats();
|
|
132
|
+
for (const [table, stats] of Object.entries(statsBefore)) {
|
|
133
|
+
console.log(` ${table}: ${stats.rows.toLocaleString()} rows (${stats.size})`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Cleanup
|
|
137
|
+
const actionsDeleted = await this.cleanupKeeperActions();
|
|
138
|
+
const healthDeleted = await this.cleanupKeeperHealth();
|
|
139
|
+
const roundsArchived = await this.archiveOldIncompleteRounds();
|
|
140
|
+
|
|
141
|
+
// Vacuum
|
|
142
|
+
await this.vacuum();
|
|
143
|
+
|
|
144
|
+
// Show stats after cleanup
|
|
145
|
+
console.log('\nš After Cleanup:');
|
|
146
|
+
const statsAfter = await this.getTableStats();
|
|
147
|
+
for (const [table, stats] of Object.entries(statsAfter)) {
|
|
148
|
+
const before = statsBefore[table].rows;
|
|
149
|
+
const after = stats.rows;
|
|
150
|
+
const diff = before - after;
|
|
151
|
+
const diffStr = diff > 0 ? ` (-${diff})` : '';
|
|
152
|
+
console.log(` ${table}: ${stats.rows.toLocaleString()} rows${diffStr} (${stats.size})`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Summary
|
|
156
|
+
console.log('\n' + '='.repeat(80));
|
|
157
|
+
console.log('ā
CLEANUP COMPLETE');
|
|
158
|
+
console.log('='.repeat(80));
|
|
159
|
+
console.log(`
|
|
160
|
+
Summary:
|
|
161
|
+
- ${actionsDeleted} action logs deleted
|
|
162
|
+
- ${healthDeleted} health snapshots deleted
|
|
163
|
+
- ${roundsArchived} incomplete rounds archived
|
|
164
|
+
- keeper_rounds (resolved): Kept forever ā
|
|
165
|
+
- jackpot_rounds: Kept forever ā
|
|
166
|
+
|
|
167
|
+
Retention Policies:
|
|
168
|
+
- keeper_actions: ${RETENTION_POLICIES.keeper_actions} days
|
|
169
|
+
- keeper_health: ${RETENTION_POLICIES.keeper_health} days
|
|
170
|
+
- keeper_rounds (incomplete): ${RETENTION_POLICIES.keeper_rounds_incomplete} days
|
|
171
|
+
- keeper_rounds (resolved): Forever (audit trail)
|
|
172
|
+
- jackpot_rounds: Forever (user verification)
|
|
173
|
+
`);
|
|
174
|
+
console.log('='.repeat(80));
|
|
175
|
+
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('\nā CLEANUP FAILED:', error.message);
|
|
178
|
+
console.error(error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
} finally {
|
|
181
|
+
await this.pool.end();
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Run if called directly
|
|
187
|
+
if (require.main === module) {
|
|
188
|
+
const cleaner = new DatabaseCleaner();
|
|
189
|
+
cleaner.run();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = DatabaseCleaner;
|
|
193
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clear all notification caches from Redis
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* REDIS_URL=<your-redis-url> node scripts/clear-notification-cache.js
|
|
6
|
+
*
|
|
7
|
+
* Or via Heroku:
|
|
8
|
+
* heroku run node scripts/clear-notification-cache.js -a <app-name>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const Redis = require('ioredis');
|
|
12
|
+
|
|
13
|
+
async function clearNotificationCache() {
|
|
14
|
+
const redisUrl = process.env.REDIS_URL;
|
|
15
|
+
|
|
16
|
+
if (!redisUrl) {
|
|
17
|
+
console.error('ā REDIS_URL environment variable is required');
|
|
18
|
+
console.log('Usage: REDIS_URL=<your-redis-url> node scripts/clear-notification-cache.js');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log('š Connecting to Redis...');
|
|
23
|
+
|
|
24
|
+
const client = new Redis(redisUrl, {
|
|
25
|
+
tls: redisUrl.startsWith('rediss://') ? { rejectUnauthorized: false } : undefined,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
await client.ping();
|
|
30
|
+
console.log('ā
Connected to Redis\n');
|
|
31
|
+
|
|
32
|
+
// Find and delete all notification-related keys
|
|
33
|
+
const patterns = [
|
|
34
|
+
'notifications:*', // User notification sorted sets
|
|
35
|
+
'notification:*', // Individual notification hashes
|
|
36
|
+
'unread:*', // Unread counts
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
let totalDeleted = 0;
|
|
40
|
+
|
|
41
|
+
for (const pattern of patterns) {
|
|
42
|
+
console.log(`š Scanning for keys matching: ${pattern}`);
|
|
43
|
+
|
|
44
|
+
let cursor = '0';
|
|
45
|
+
let keysToDelete = [];
|
|
46
|
+
|
|
47
|
+
// Use SCAN to find keys (safe for production)
|
|
48
|
+
do {
|
|
49
|
+
const [newCursor, keys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
50
|
+
cursor = newCursor;
|
|
51
|
+
keysToDelete = keysToDelete.concat(keys);
|
|
52
|
+
} while (cursor !== '0');
|
|
53
|
+
|
|
54
|
+
if (keysToDelete.length > 0) {
|
|
55
|
+
console.log(` Found ${keysToDelete.length} keys`);
|
|
56
|
+
|
|
57
|
+
// Delete in batches
|
|
58
|
+
const batchSize = 100;
|
|
59
|
+
for (let i = 0; i < keysToDelete.length; i += batchSize) {
|
|
60
|
+
const batch = keysToDelete.slice(i, i + batchSize);
|
|
61
|
+
await client.del(...batch);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
totalDeleted += keysToDelete.length;
|
|
65
|
+
console.log(` ā
Deleted ${keysToDelete.length} keys`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log(` No keys found`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`\nš Done! Cleared ${totalDeleted} notification cache keys.`);
|
|
72
|
+
console.log('š Users will have their caches rebuilt from PostgreSQL on next request.');
|
|
73
|
+
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error('ā Error:', error.message);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
} finally {
|
|
78
|
+
await client.quit();
|
|
79
|
+
console.log('\nš Disconnected from Redis');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
clearNotificationCache();
|
|
84
|
+
|
|
85
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Convert mnemonic to Solana keypair
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
// Your mnemonic
|
|
5
|
+
const mnemonic = process.argv[2] || 'abuse comfort amazing live royal auto target quick damage flavor rug dress';
|
|
6
|
+
|
|
7
|
+
console.log('š Converting mnemonic to Solana keypair...\n');
|
|
8
|
+
console.log('Mnemonic:', mnemonic);
|
|
9
|
+
console.log('');
|
|
10
|
+
|
|
11
|
+
// Using solana-keygen programmatically
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
// Create a temp file with the mnemonic
|
|
16
|
+
const tempFile = '/tmp/temp-mnemonic.txt';
|
|
17
|
+
fs.writeFileSync(tempFile, mnemonic);
|
|
18
|
+
|
|
19
|
+
// Use solana-keygen to recover
|
|
20
|
+
const output = execSync(`solana-keygen recover -o wallets/oracle-new.json --force 'prompt://key=0/0' < ${tempFile}`, {
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
22
|
+
}).toString();
|
|
23
|
+
|
|
24
|
+
// Get public key
|
|
25
|
+
const pubkey = execSync('solana-keygen pubkey wallets/oracle-new.json').toString().trim();
|
|
26
|
+
|
|
27
|
+
console.log('ā
Keypair recovered!');
|
|
28
|
+
console.log('Public Key:', pubkey);
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log('Expected: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa');
|
|
31
|
+
console.log('Match:', pubkey === 'FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa' ? 'ā
YES' : 'ā NO');
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log('Saved to: wallets/oracle-new.json');
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('To use:');
|
|
36
|
+
console.log(' mv wallets/oracle-new.json wallets/oracle.json');
|
|
37
|
+
|
|
38
|
+
// Clean up
|
|
39
|
+
fs.unlinkSync(tempFile);
|
|
40
|
+
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Error:', error.message);
|
|
43
|
+
console.log('\nš MANUAL STEPS:\n');
|
|
44
|
+
console.log('1. Run this command:');
|
|
45
|
+
console.log(' solana-keygen recover -o wallets/oracle.json --force\n');
|
|
46
|
+
console.log('2. When prompted, enter your mnemonic phrase\n');
|
|
47
|
+
console.log('3. Press Enter for default derivation path\n');
|
|
48
|
+
console.log('4. Verify public key matches: FWUJCthDfPcgmTvdQWM5uofxxiYjqJFMMwiLYvS7LBFa\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
-- Users table for wallet-based authentication
|
|
2
|
+
-- Run this to create the users table in your Postgres database
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
5
|
+
id SERIAL PRIMARY KEY,
|
|
6
|
+
wallet_address VARCHAR(44) UNIQUE NOT NULL,
|
|
7
|
+
email VARCHAR(255),
|
|
8
|
+
username VARCHAR(50) NOT NULL,
|
|
9
|
+
avatar TEXT,
|
|
10
|
+
referral_code VARCHAR(50),
|
|
11
|
+
signature TEXT,
|
|
12
|
+
onboarding_complete BOOLEAN DEFAULT false,
|
|
13
|
+
created_at TIMESTAMP DEFAULT NOW(),
|
|
14
|
+
updated_at TIMESTAMP DEFAULT NOW()
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
-- Nonces table for secure signature verification (prevents replay attacks)
|
|
18
|
+
CREATE TABLE IF NOT EXISTS auth_nonces (
|
|
19
|
+
wallet_address VARCHAR(44) PRIMARY KEY,
|
|
20
|
+
nonce VARCHAR(64) NOT NULL,
|
|
21
|
+
expires_at TIMESTAMP NOT NULL,
|
|
22
|
+
used BOOLEAN DEFAULT false,
|
|
23
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
-- Index for faster wallet lookups
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_users_wallet ON users(wallet_address);
|
|
28
|
+
|
|
29
|
+
-- Index for username lookups
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
31
|
+
|
|
32
|
+
-- Index for nonce cleanup
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_nonces_expires ON auth_nonces(expires_at);
|
|
34
|
+
|
|
35
|
+
COMMENT ON TABLE users IS 'User profiles with wallet-based authentication';
|
|
36
|
+
COMMENT ON COLUMN users.wallet_address IS 'Solana wallet public key (base58)';
|
|
37
|
+
COMMENT ON COLUMN users.signature IS 'Signature proving wallet ownership';
|
|
38
|
+
COMMENT ON COLUMN users.onboarding_complete IS 'Whether user has completed onboarding flow';
|
|
39
|
+
|
|
40
|
+
COMMENT ON TABLE auth_nonces IS 'One-time nonces for signature verification (prevents replay attacks)';
|
|
41
|
+
COMMENT ON COLUMN auth_nonces.nonce IS 'Cryptographically secure random nonce';
|
|
42
|
+
COMMENT ON COLUMN auth_nonces.used IS 'Whether this nonce has been used';
|
|
43
|
+
COMMENT ON COLUMN auth_nonces.expires_at IS 'Nonce expiration time (5 minutes)';
|
|
44
|
+
|