From a82ec3c9fca5f1edb003fdbe556026596ab572b5 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Tue, 31 Mar 2026 16:57:36 -0700 Subject: [PATCH] chore: add production database cleanup scripts --- package.json | 1 + scripts/check-test-records.js | 111 +++++++++++++++++++++++++++++ scripts/cleanup-prod-db.js | 130 ++++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100755 scripts/check-test-records.js create mode 100755 scripts/cleanup-prod-db.js diff --git a/package.json b/package.json index 0d6d3fb..f041b93 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "db:setup-dev:clean": "DATABASE_URL=\"postgresql://euchre_camp:LINGO5row_hiding@dhg.lol:5432/euchre_camp_dev\" node scripts/setup-postgres.js --drop", "db:reset-dev": "node scripts/reset-dev-db.js", "db:use-dev": "node scripts/use-dev-db.js", + "db:cleanup-prod": "node scripts/cleanup-prod-db.js", "db:seed": "node scripts/seed.js", "docker:up": "docker-compose up -d", "docker:down": "docker-compose down", diff --git a/scripts/check-test-records.js b/scripts/check-test-records.js new file mode 100755 index 0000000..e57ecfd --- /dev/null +++ b/scripts/check-test-records.js @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +/** + * Check for test records in production database (read-only) + * Shows what would be deleted without making any changes + */ + +const { execSync } = require('child_process'); + +// Database connection string +const DB_URL = 'postgresql://euchre_camp:LINGO5row_hiding@dhg.lol:5432/euchre_camp'; + +// Helper to run SQL and log results +function runSQL(sql, description) { + console.log(`\n๐Ÿ” ${description}...`); + try { + const result = execSync(`psql "${DB_URL}" -c "${sql}"`, { encoding: 'utf8' }); + console.log(result); + return true; + } catch (error) { + console.error(`โŒ Error: ${error.message}`); + return false; + } +} + +// Helper to count records matching pattern +function countRecords(table, column, pattern) { + const sql = `SELECT COUNT(*) FROM ${table} WHERE ${column} LIKE '${pattern}';`; + try { + const result = execSync(`psql "${DB_URL}" -c "${sql}" -t`, { encoding: 'utf8' }).trim(); + return parseInt(result); + } catch (error) { + return 0; + } +} + +console.log('๐Ÿ” Checking test records in production database'); +console.log('=============================================\n'); + +// Count test players +const testPlayerCount = countRecords('players', 'name', '%Test%') + + countRecords('players', 'name', '%Setup%') + + countRecords('players', 'name', '%Home Test%'); +console.log(`Test players found: ${testPlayerCount}`); + +// Count test tournaments +const testEventCount = countRecords('events', 'name', '%Test%') + + countRecords('events', 'name', '%Setup%'); +console.log(`Test tournaments found: ${testEventCount}`); + +// Count test users +const testUserCount = countRecords('users', 'email', '%test%') + + countRecords('users', 'email', '%setup%'); +console.log(`Test users found: ${testUserCount}`); + +// Show test players +runSQL(` + SELECT id, name + FROM players + WHERE name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%' + ORDER BY name; +`, 'Test players list'); + +// Show test events +runSQL(` + SELECT id, name + FROM events + WHERE name LIKE '%Test%' OR name LIKE '%Setup%' + ORDER BY name; +`, 'Test events list'); + +// Show test users +runSQL(` + SELECT id, email, name + FROM users + WHERE email LIKE '%test%' OR email LIKE '%setup%' + ORDER BY email; +`, 'Test users list'); + +// Check which test players have matches +runSQL(` + SELECT p.id, p.name + FROM players p + WHERE (p.name LIKE '%Test%' OR p.name LIKE '%Setup%' OR p.name LIKE '%Home Test%') + AND EXISTS ( + SELECT 1 FROM matches m + WHERE m."team1P1Id" = p.id + OR m."team1P2Id" = p.id + OR m."team2P1Id" = p.id + OR m."team2P2Id" = p.id + ) + ORDER BY p.name; +`, 'Test players with matches (will be preserved)'); + +// Check which test events have matches +runSQL(` + SELECT e.id, e.name + FROM events e + WHERE (e.name LIKE '%Test%' OR e.name LIKE '%Setup%') + AND EXISTS ( + SELECT 1 FROM matches m WHERE m."eventId" = e.id + ) + ORDER BY e.name; +`, 'Test events with matches (will be preserved)'); + +console.log('\nโœ… Check complete!'); +console.log('\n๐Ÿ’ก Summary:'); +console.log(' - Test players without matches can be deleted'); +console.log(' - Test tournaments without matches can be deleted'); +console.log(' - Test users without player associations can be deleted'); +console.log(' - Records with matches/associations are preserved'); diff --git a/scripts/cleanup-prod-db.js b/scripts/cleanup-prod-db.js new file mode 100755 index 0000000..b6b126e --- /dev/null +++ b/scripts/cleanup-prod-db.js @@ -0,0 +1,130 @@ +#!/usr/bin/env node + +/** + * Clean up production database - remove test records only + * + * WARNING: This script modifies the production database. + * It only removes records that match test patterns. + * It preserves actual player data, matches, and tournaments. + */ + +const { execSync } = require('child_process'); + +// Database connection string +const DB_URL = 'postgresql://euchre_camp:LINGO5row_hiding@dhg.lol:5432/euchre_camp'; + +// Helper to run SQL and log results +function runSQL(sql, description) { + console.log(`\n๐Ÿ” ${description}...`); + try { + const result = execSync(`psql "${DB_URL}" -c "${sql}"`, { encoding: 'utf8' }); + console.log(result); + return true; + } catch (error) { + console.error(`โŒ Error: ${error.message}`); + return false; + } +} + +// Helper to run multi-line SQL +function runMultiLineSQL(sqlLines, description) { + console.log(`\n๐Ÿ” ${description}...`); + try { + const sql = sqlLines.join('\n'); + const result = execSync(`psql "${DB_URL}" -c "${sql}"`, { encoding: 'utf8' }); + console.log(result); + return true; + } catch (error) { + console.error(`โŒ Error: ${error.message}`); + return false; + } +} + +// Helper to count records matching pattern +function countRecords(table, column, pattern) { + const sql = `SELECT COUNT(*) FROM ${table} WHERE ${column} LIKE '${pattern}';`; + try { + const result = execSync(`psql "${DB_URL}" -c "${sql}" -t`, { encoding: 'utf8' }).trim(); + return parseInt(result); + } catch (error) { + return 0; + } +} + +async function main() { + console.log('๐Ÿงน Cleaning up test records from production database'); + console.log('=============================================\n'); + + // Confirm with user + const readline = require('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + rl.question('โš ๏ธ This will DELETE test records from PRODUCTION database. Continue? (y/N) ', async (answer) => { + if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') { + console.log('โŒ Cleanup cancelled'); + rl.close(); + process.exit(0); + } + + rl.close(); + + console.log('\n๐Ÿ“‹ Checking test records...\n'); + + // Count test players + const testPlayerCount = countRecords('players', 'name', '%Test%') + + countRecords('players', 'name', '%Setup%') + + countRecords('players', 'name', '%Home Test%'); + console.log(`Test players found: ${testPlayerCount}`); + + // Count test tournaments + const testEventCount = countRecords('events', 'name', '%Test%') + + countRecords('events', 'name', '%Setup%'); + console.log(`Test tournaments found: ${testEventCount}`); + + // Count test users + const testUserCount = countRecords('users', 'email', '%test%') + + countRecords('users', 'email', '%setup%'); + console.log(`Test users found: ${testUserCount}`); + + console.log('\n๐Ÿ”„ Starting cleanup...\n'); + + // Step 1: Delete test events (tournaments) that have no matches + console.log('1. Deleting test tournaments without matches...'); + runSQL( + "DELETE FROM events WHERE (name LIKE '%Test%' OR name LIKE '%Setup%') AND NOT EXISTS (SELECT 1 FROM matches WHERE matches.\"eventId\" = events.id);", + 'Deleted test tournaments' + ); + + // Step 2: Delete test players that have no matches + console.log('\n2. Deleting test players without matches...'); + runSQL( + "DELETE FROM players WHERE (name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%') AND NOT EXISTS (SELECT 1 FROM matches WHERE matches.\"team1P1Id\" = players.id) AND NOT EXISTS (SELECT 1 FROM matches WHERE matches.\"team1P2Id\" = players.id) AND NOT EXISTS (SELECT 1 FROM matches WHERE matches.\"team2P1Id\" = players.id) AND NOT EXISTS (SELECT 1 FROM matches WHERE matches.\"team2P2Id\" = players.id);", + 'Deleted test players without matches' + ); + + // Step 3: Delete test users (they shouldn't have player associations) + console.log('\n3. Deleting test users...'); + runSQL( + "DELETE FROM users WHERE (email LIKE '%test%' OR email LIKE '%setup%') AND \"playerId\" IS NULL;", + 'Deleted test users' + ); + + // Summary + console.log('\nโœ… Cleanup complete!'); + console.log('\n๐Ÿ“Š Remaining test records:'); + runSQL("SELECT COUNT(*) FROM players WHERE name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%';", 'Remaining test players'); + runSQL("SELECT COUNT(*) FROM events WHERE name LIKE '%Test%' OR name LIKE '%Setup%';", 'Remaining test tournaments'); + runSQL("SELECT COUNT(*) FROM users WHERE email LIKE '%test%' OR email LIKE '%setup%';", 'Remaining test users'); + + console.log('\n๐Ÿ’ก Note: Some test records may remain if they have:'); + console.log(' - Associated matches'); + console.log(' - Associated users'); + console.log(' These were preserved to maintain data integrity.'); + }); +} + +// Run main function +main();