const { query } = require('./config/database'); const fs = require('fs'); const path = require('path'); async function validateAlignment() { console.log('šŸ” Validating Database-Backend Alignment...\n'); const issues = []; const warnings = []; const successes = []; try { // 1. Check required tables exist console.log('1ļøāƒ£ Checking required tables...'); const requiredTables = [ 'products', 'product_images', 'blogposts', 'pages', 'portfolioprojects', 'adminusers', 'customers', 'orders', 'order_items', 'product_reviews' ]; for (const table of requiredTables) { const exists = await query(` SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_name = $1 ) as exists `, [table]); if (exists.rows[0].exists) { successes.push(`āœ“ Table ${table} exists`); } else { issues.push(`āœ— Missing table: ${table}`); } } // 2. Check foreign key relationships console.log('\n2ļøāƒ£ Checking foreign key relationships...'); const relationships = [ { table: 'product_images', column: 'product_id', ref: 'products' }, { table: 'order_items', column: 'order_id', ref: 'orders' }, { table: 'order_items', column: 'product_id', ref: 'products' }, { table: 'product_reviews', column: 'product_id', ref: 'products' }, { table: 'product_reviews', column: 'customer_id', ref: 'customers' } ]; for (const rel of relationships) { const exists = await query(` SELECT EXISTS ( SELECT 1 FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name = $1 AND kcu.column_name = $2 ) as exists `, [rel.table, rel.column]); if (exists.rows[0].exists) { successes.push(`āœ“ FK: ${rel.table}.${rel.column} -> ${rel.ref}`); } else { warnings.push(`⚠ Missing FK: ${rel.table}.${rel.column} -> ${rel.ref}`); } } // 3. Check critical indexes console.log('\n3ļøāƒ£ Checking critical indexes...'); const criticalIndexes = [ { table: 'products', column: 'slug' }, { table: 'products', column: 'isactive' }, { table: 'product_images', column: 'product_id' }, { table: 'blogposts', column: 'slug' }, { table: 'pages', column: 'slug' }, { table: 'orders', column: 'ordernumber' } ]; for (const idx of criticalIndexes) { const exists = await query(` SELECT EXISTS ( SELECT 1 FROM pg_indexes WHERE tablename = $1 AND indexdef LIKE '%' || $2 || '%' ) as exists `, [idx.table, idx.column]); if (exists.rows[0].exists) { successes.push(`āœ“ Index on ${idx.table}.${idx.column}`); } else { warnings.push(`⚠ Missing index on ${idx.table}.${idx.column}`); } } // 4. Check constraints console.log('\n4ļøāƒ£ Checking data constraints...'); const constraints = [ { table: 'products', name: 'chk_products_price_positive' }, { table: 'products', name: 'chk_products_stock_nonnegative' }, { table: 'product_images', name: 'chk_product_images_order_nonnegative' } ]; for (const con of constraints) { const exists = await query(` SELECT EXISTS ( SELECT 1 FROM information_schema.table_constraints WHERE constraint_name = $1 AND table_name = $2 ) as exists `, [con.name, con.table]); if (exists.rows[0].exists) { successes.push(`āœ“ Constraint ${con.name}`); } else { issues.push(`āœ— Missing constraint: ${con.name}`); } } // 5. Check CASCADE delete setup console.log('\n5ļøāƒ£ Checking CASCADE delete rules...'); const cascades = await query(` SELECT tc.table_name, kcu.column_name, ccu.table_name AS foreign_table_name, rc.delete_rule FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name JOIN information_schema.referential_constraints AS rc ON rc.constraint_name = tc.constraint_name WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_name IN ('product_images', 'order_items', 'product_reviews') `); cascades.rows.forEach(row => { if (row.delete_rule === 'CASCADE') { successes.push(`āœ“ CASCADE delete: ${row.table_name}.${row.column_name}`); } else { warnings.push(`⚠ Non-CASCADE delete: ${row.table_name}.${row.column_name} (${row.delete_rule})`); } }); // 6. Test query performance console.log('\n6ļøāƒ£ Testing query performance...'); const start = Date.now(); await query(` SELECT p.*, COALESCE( json_agg( json_build_object('id', pi.id, 'image_url', pi.image_url) ORDER BY pi.display_order ) FILTER (WHERE pi.id IS NOT NULL), '[]'::json ) as images FROM products p LEFT JOIN product_images pi ON pi.product_id = p.id WHERE p.isactive = true GROUP BY p.id LIMIT 10 `); const duration = Date.now() - start; if (duration < 100) { successes.push(`āœ“ Query performance: ${duration}ms (excellent)`); } else if (duration < 300) { successes.push(`āœ“ Query performance: ${duration}ms (good)`); } else { warnings.push(`⚠ Query performance: ${duration}ms (needs optimization)`); } // 7. Check data integrity console.log('\n7ļøāƒ£ Checking data integrity...'); // Orphaned product images const orphanedImages = await query(` SELECT COUNT(*) as count FROM product_images pi LEFT JOIN products p ON p.id = pi.product_id WHERE p.id IS NULL `); if (orphanedImages.rows[0].count === '0') { successes.push('āœ“ No orphaned product images'); } else { warnings.push(`⚠ ${orphanedImages.rows[0].count} orphaned product images`); } // Products without images const noImages = await query(` SELECT COUNT(*) as count FROM products p LEFT JOIN product_images pi ON pi.product_id = p.id WHERE p.isactive = true AND pi.id IS NULL `); if (noImages.rows[0].count === '0') { successes.push('āœ“ All active products have images'); } else { warnings.push(`⚠ ${noImages.rows[0].count} active products without images`); } // Summary console.log('\n' + '='.repeat(60)); console.log('šŸ“Š VALIDATION SUMMARY'); console.log('='.repeat(60)); console.log(`\nāœ… Successes: ${successes.length}`); if (successes.length > 0 && successes.length <= 10) { successes.forEach(s => console.log(` ${s}`)); } else if (successes.length > 10) { console.log(` (${successes.length} items validated successfully)`); } if (warnings.length > 0) { console.log(`\nāš ļø Warnings: ${warnings.length}`); warnings.forEach(w => console.log(` ${w}`)); } if (issues.length > 0) { console.log(`\nāŒ Issues: ${issues.length}`); issues.forEach(i => console.log(` ${i}`)); } console.log('\n' + '='.repeat(60)); if (issues.length === 0) { console.log('\nšŸŽ‰ Database is properly aligned with backend!\n'); process.exit(0); } else { console.log('\nāš ļø Database has issues that need attention.\n'); process.exit(1); } } catch (error) { console.error('\nāŒ Validation error:', error.message); process.exit(1); } } validateAlignment();