import dotenv from 'dotenv'; import fs from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { Pool } from 'pg'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables dotenv.config({ path: join(__dirname, '..', '.env') }); // Database configuration const dbConfig = { host: process.env.LOCAL_DB_HOST || '192.168.8.151', port: parseInt(process.env.LOCAL_DB_PORT || '5432'), user: process.env.LOCAL_DB_USER || 'postgres', password: process.env.LOCAL_DB_PASSWORD || 'postgres', database: process.env.LOCAL_DB_NAME || 'institutional_trader' }; /** * Execute SQL file or query */ async function executeSQL(query, params = []) { const pool = new Pool(dbConfig); try { const result = await pool.query(query, params); return result.rows; } finally { await pool.end(); } } /** * Run analysis queries to show current state */ async function analyzeDatabase() { console.log('\nšŸ“Š Analyzing database date/time formats...\n'); const queries = { 'CreatedDate Analysis': ` SELECT COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date, COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format, COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "CreatedDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format, COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_dates, COUNT(*) as total_rows FROM "OptionsFlow_monthly" `, 'CreatedTime Analysis': ` SELECT COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_with_space, COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}[AP]M$') as valid_no_space, COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as valid_minutes_only_space, COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}[AP]M$') as valid_minutes_only_no_space, COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~* '(AM|PM)') as no_ampm, COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}\\s*[AP]M$' AND "CreatedTime"::text !~ ':') as missing_colon, COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_times, COUNT(*) as total_rows FROM "OptionsFlow_monthly" `, 'ExpirationDate Analysis': ` SELECT COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_iso_date, COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as slash_format, COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$' AND "ExpirationDate"::text !~ '^\\d{1,2}/\\d{1,2}/\\d{2,4}$') as invalid_format, COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_dates, COUNT(*) as total_rows FROM "OptionsFlow_monthly" ` }; for (const [name, query] of Object.entries(queries)) { console.log(`\n${name}:`); console.log('─'.repeat(50)); try { const results = await executeSQL(query); if (results.length > 0) { console.table(results[0]); } } catch (error) { console.error(`Error running ${name}:`, error.message); } } } /** * Run the fix script */ async function runFix() { console.log('\nšŸ”§ Running date/time fixes...\n'); const sqlFile = join(__dirname, '..', 'database', 'fix_all_dates_and_times.sql'); const sql = fs.readFileSync(sqlFile, 'utf8'); const pool = new Pool(dbConfig); try { // Extract and execute UPDATE statements only (skip SELECT/verification) const updateStatements = sql .split(/;[\r\n]+/) .map(s => s.trim()) .filter(s => { const upper = s.toUpperCase().trim(); return upper.startsWith('UPDATE') && !upper.includes('-- Skip'); }); console.log(`Found ${updateStatements.length} UPDATE statements to execute...\n`); for (let i = 0; i < updateStatements.length; i++) { const statement = updateStatements[i]; try { const result = await pool.query(statement); console.log(`āœ… Update ${i + 1}/${updateStatements.length}: ${result.rowCount || 0} rows affected`); } catch (error) { console.error(`āŒ Error in update ${i + 1}:`, error.message); // Continue with next statement } } console.log('\nāœ… All fix statements executed!\n'); } finally { await pool.end(); } } /** * Verify fixes */ async function verifyFixes() { console.log('\nāœ… Verifying fixes...\n'); const queries = { 'CreatedDate Verification': ` SELECT COUNT(*) FILTER (WHERE "CreatedDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format, COUNT(*) FILTER (WHERE "CreatedDate" IS NOT NULL AND "CreatedDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid, COUNT(*) FILTER (WHERE "CreatedDate" IS NULL) as null_count, COUNT(*) as total_rows FROM "OptionsFlow_monthly" `, 'CreatedTime Verification': ` SELECT COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}:\\d{2}\\s+[AP]M$') as valid_format, COUNT(*) FILTER (WHERE "CreatedTime"::text ~ '^\\d{1,2}:\\d{2}\\s+[AP]M$') as minutes_only_valid, COUNT(*) FILTER (WHERE "CreatedTime" IS NOT NULL AND "CreatedTime"::text !~ '^\\d{1,2}:\\d{2}(:\\d{2})?\\s+[AP]M$') as still_invalid, COUNT(*) FILTER (WHERE "CreatedTime" IS NULL) as null_count, COUNT(*) as total_rows FROM "OptionsFlow_monthly" `, 'ExpirationDate Verification': ` SELECT COUNT(*) FILTER (WHERE "ExpirationDate"::text ~ '^\\d{4}-\\d{2}-\\d{2}$') as valid_format, COUNT(*) FILTER (WHERE "ExpirationDate" IS NOT NULL AND "ExpirationDate"::text !~ '^\\d{4}-\\d{2}-\\d{2}$') as still_invalid, COUNT(*) FILTER (WHERE "ExpirationDate" IS NULL) as null_count, COUNT(*) as total_rows FROM "OptionsFlow_monthly" ` }; for (const [name, query] of Object.entries(queries)) { console.log(`\n${name}:`); console.log('─'.repeat(50)); try { const results = await executeSQL(query); if (results.length > 0) { const row = results[0]; console.table(row); // Show percentage if (row.total_rows > 0) { const validPct = ((row.valid_format || 0) / row.total_rows * 100).toFixed(2); const invalidPct = ((row.still_invalid || 0) / row.total_rows * 100).toFixed(2); console.log(` Valid: ${validPct}% | Invalid: ${invalidPct}%`); } } } catch (error) { console.error(`Error running ${name}:`, error.message); } } } /** * Main function */ async function main() { const args = process.argv.slice(2); const command = args[0] || 'all'; console.log('šŸ—„ļø Database Date/Time Fix Script\n'); console.log(`Connecting to: ${dbConfig.host}:${dbConfig.port}/${dbConfig.database}\n`); try { if (command === 'analyze' || command === 'all') { await analyzeDatabase(); } if (command === 'fix' || command === 'all') { if (command === 'all') { console.log('\n' + '='.repeat(60)); console.log('Press Enter to continue with fixes, or Ctrl+C to cancel...'); // Note: In a real implementation, you might want to add a prompt here } await runFix(); } if (command === 'verify' || command === 'all') { await verifyFixes(); } console.log('\n✨ Done!\n'); } catch (error) { console.error('\nāŒ Error:', error.message); process.exit(1); } } // Run if executed directly if (import.meta.url === `file://${process.argv[1]}`) { main(); } export { analyzeDatabase, runFix, verifyFixes };