diff --git a/scripts/cleanup-prod-db.js b/scripts/cleanup-prod-db.js index c8fa0fa..e47342d 100755 --- a/scripts/cleanup-prod-db.js +++ b/scripts/cleanup-prod-db.js @@ -29,6 +29,46 @@ if (DB_URL.includes('_dev') || DB_URL.includes('_dev_')) { process.exit(1); } +// Single source of truth for test object patterns +const TEST_PATTERNS = { + players: [ + '%Test%', + '%Setup%', + '%Home Test%', + '%Home Match Player%', + '%Admin User%', + '%NinePart%', + '%Nine Part%', + '%Test Player%', + '%TestUser%', + '%Cucumber%', + '%Config Admin%', + ], + events: [ + '%Test%', + '%Setup%', + '%Recent%', + '%Test Tournament%', + '%Cucumber%', + ], + users: [ + '%test%', + '%setup%', + '%cucumber%', + '%TestUser%', + ] +}; + +// Helper to build SQL LIKE clause from patterns +function buildLikeClause(patterns) { + return patterns.map(p => `name LIKE '${p}'`).join(' OR '); +} + +// Helper to build email LIKE clause +function buildEmailLikeClause(patterns) { + return patterns.map(p => `email LIKE '${p}'`).join(' OR '); +} + // Helper to run SQL and log results function runSQL(sql, description) { console.log(`\nšŸ” ${description}...`); @@ -42,13 +82,23 @@ function runSQL(sql, description) { } } -// Helper to run multi-line SQL -function runMultiLineSQL(sqlLines, description) { +// Helper to run SQL via file (avoids quote escaping issues) +function runSQLViaFile(sql, description) { console.log(`\nšŸ” ${description}...`); + const fs = require('fs'); + const os = require('os'); + const path = require('path'); + try { - const sql = sqlLines.join('\n'); - const result = execSync(`psql "${DB_URL}" -c "${sql}"`, { encoding: 'utf8' }); + // Create temp file with SQL + const tmpFile = path.join(os.tmpdir(), `cleanup_${Date.now()}.sql`); + fs.writeFileSync(tmpFile, sql); + + const result = execSync(`psql "${DB_URL}" -f "${tmpFile}"`, { encoding: 'utf8' }); console.log(result); + + // Clean up temp file + fs.unlinkSync(tmpFile); return true; } catch (error) { console.error(`āŒ Error: ${error.message}`); @@ -56,9 +106,12 @@ function runMultiLineSQL(sqlLines, description) { } } -// Helper to count records matching pattern -function countRecords(table, column, pattern) { - const sql = `SELECT COUNT(*) FROM ${table} WHERE ${column} LIKE '${pattern}';`; +// Helper to count records matching any of the patterns +function countRecordsByPatterns(table, patterns) { + const whereClause = table === 'users' + ? buildEmailLikeClause(patterns) + : buildLikeClause(patterns); + const sql = `SELECT COUNT(*) FROM ${table} WHERE ${whereClause};`; try { const result = execSync(`psql "${DB_URL}" -c "${sql}" -t`, { encoding: 'utf8' }).trim(); return parseInt(result); @@ -89,57 +142,67 @@ async function main() { 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%') + - countRecords('players', 'name', '%Home Match Player%') + - countRecords('players', 'name', '%Admin User%'); + // Count test records using centralized patterns + const testPlayerCount = countRecordsByPatterns('players', TEST_PATTERNS.players); console.log(`Test players found: ${testPlayerCount}`); - // Count test tournaments - const testEventCount = countRecords('events', 'name', '%Test%') + - countRecords('events', 'name', '%Setup%') + - countRecords('events', 'name', '%Recent%'); + const testEventCount = countRecordsByPatterns('events', TEST_PATTERNS.events); console.log(`Test tournaments found: ${testEventCount}`); - // Count test users - const testUserCount = countRecords('users', 'email', '%test%') + - countRecords('users', 'email', '%setup%'); + const testUserCount = countRecordsByPatterns('users', TEST_PATTERNS.users); console.log(`Test users found: ${testUserCount}`); console.log('\nšŸ”„ Starting cleanup...\n'); // Step 1: Delete test tournaments (with matches due to cascade delete) console.log('1. Deleting test tournaments (matches will be deleted via cascade)...'); - runSQL( - "DELETE FROM events WHERE (name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Recent%');", + const eventWhere = buildLikeClause(TEST_PATTERNS.events); + runSQLViaFile( + `DELETE FROM events WHERE (${eventWhere});`, 'Deleted test tournaments' ); // Step 2: Delete test players (all of them, since we deleted their matches) console.log('\n2. Deleting test players...'); - runSQL( - "DELETE FROM players WHERE (name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%' OR name LIKE '%Home Match Player%' OR name LIKE '%Admin User%');", + const playerWhere = buildLikeClause(TEST_PATTERNS.players); + runSQLViaFile( + `DELETE FROM players WHERE (${playerWhere});`, 'Deleted test players' ); // 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;", + const userWhere = buildEmailLikeClause(TEST_PATTERNS.users); + runSQLViaFile( + `DELETE FROM users WHERE (${userWhere}) 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'); + + const remainingPlayerWhere = buildLikeClause(TEST_PATTERNS.players); + runSQLViaFile( + `SELECT COUNT(*) FROM players WHERE ${remainingPlayerWhere};`, + 'Remaining test players' + ); + + const remainingEventWhere = buildLikeClause(TEST_PATTERNS.events); + runSQLViaFile( + `SELECT COUNT(*) FROM events WHERE ${remainingEventWhere};`, + 'Remaining test tournaments' + ); + + const remainingUserWhere = buildEmailLikeClause(TEST_PATTERNS.users); + runSQLViaFile( + `SELECT COUNT(*) FROM users WHERE ${remainingUserWhere};`, + 'Remaining test users' + ); console.log('\nšŸ’” Note: All test records deleted. Matches are automatically deleted'); console.log(' via cascade delete when their parent tournament is deleted.'); + console.log('\nšŸ“ To add new test patterns, edit TEST_PATTERNS in this script.'); }); } diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index b47fdda..57a164b 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,13 +1,11 @@ import { PrismaClient } from '@prisma/client' -// Load .env file if it exists -require('dotenv').config() - const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined } // Detect database provider from environment (default to sqlite for local development) +// Next.js automatically loads environment variables from .env, .env.development, .env.production const databaseProvider = process.env.DATABASE_PROVIDER || 'sqlite' const databaseUrl = process.env.DATABASE_URL