Fix: Restore website functionality - all pages and APIs working

This commit is contained in:
Local Server
2026-01-14 07:16:04 -06:00
parent dc58a8ae5f
commit 9f659a2c59
41 changed files with 10890 additions and 3029 deletions

View File

@@ -17,10 +17,58 @@ const pool = new Pool({
keepAlive: true, // TCP keepalive
keepAliveInitialDelayMillis: 10000,
statement_timeout: 30000, // 30s query timeout
query_timeout: 30000, // SAFEGUARD: Force query timeout at pool level
});
pool.on("connect", () => logger.info("✓ PostgreSQL connected"));
pool.on("error", (err) => logger.error("PostgreSQL error:", err));
// SAFEGUARD: Track pool health
let poolConnected = false;
let connectionAttempts = 0;
const MAX_CONNECTION_ATTEMPTS = 3;
pool.on("connect", (client) => {
poolConnected = true;
connectionAttempts = 0;
logger.info("✓ PostgreSQL connected", {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
});
});
pool.on("error", (err, client) => {
poolConnected = false;
connectionAttempts++;
logger.error("💥 PostgreSQL pool error", {
error: err.message,
code: err.code,
attempts: connectionAttempts,
pool: {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
},
});
// SAFEGUARD: Critical failure detection
if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {
logger.error(
"🚨 Database connection critically unstable - manual intervention required"
);
}
});
pool.on("acquire", (client) => {
logger.debug("Pool client acquired", {
total: pool.totalCount,
idle: pool.idleCount,
});
});
pool.on("release", (err, client) => {
if (err) {
logger.warn("Client released with error", { error: err.message });
}
});
// Query cache for SELECT statements with crypto-based keys
const queryCache = new Map();
@@ -28,6 +76,7 @@ const queryCacheOrder = []; // LRU tracking
const QUERY_CACHE_TTL = 15000; // 15 seconds (increased)
const QUERY_CACHE_MAX_SIZE = 500; // 500 cached queries (increased)
const SLOW_QUERY_THRESHOLD = 50; // 50ms threshold (stricter)
const QUERY_TIMEOUT = 35000; // SAFEGUARD: 35s query timeout (slightly higher than pool's 30s)
// Generate fast cache key using crypto hash
const getCacheKey = (text, params) => {
@@ -53,7 +102,22 @@ const query = async (text, params) => {
}
try {
const res = await pool.query(text, params);
// SAFEGUARD: Add query timeout wrapper
const queryPromise = pool.query(text, params);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(
new Error(
`Query timeout after ${QUERY_TIMEOUT}ms: ${text.substring(
0,
50
)}...`
)
);
}, QUERY_TIMEOUT);
});
const res = await Promise.race([queryPromise, timeoutPromise]);
const duration = Date.now() - start;
// Cache SELECT queries with LRU eviction
@@ -84,11 +148,22 @@ const query = async (text, params) => {
} catch (error) {
const duration = Date.now() - start;
logger.error("Query error", {
text: text.substring(0, 100),
error: error.message,
duration,
code: error.code,
duration,
text: text.substring(0, 100),
});
// SAFEGUARD: Clear potentially corrupted cache entry
if (isSelect) {
const cacheKey = getCacheKey(text, params);
queryCache.delete(cacheKey);
const index = queryCacheOrder.indexOf(cacheKey);
if (index > -1) {
queryCacheOrder.splice(index, 1);
}
}
throw error;
}
};
@@ -141,34 +216,75 @@ const clearQueryCache = (pattern) => {
};
// Health check with pool metrics
const healthCheck = async () => {
try {
const result = await query(
"SELECT NOW() as time, current_database() as database"
const healthCheck = async (timeoutMs = 5000) => {
// SAFEGUARD: Wrap health check in timeout promise
const healthPromise = (async () => {
try {
const result = await query(
"SELECT NOW() as time, current_database() as database"
);
return {
healthy: true,
database: result.rows[0].database,
timestamp: result.rows[0].time,
pool: {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
connected: poolConnected,
},
cache: {
size: queryCache.size,
maxSize: QUERY_CACHE_MAX_SIZE,
},
};
} catch (error) {
logger.error("Database health check failed:", error);
return {
healthy: false,
error: error.message,
pool: {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
connected: poolConnected,
},
};
}
})();
// SAFEGUARD: Add timeout protection
const timeoutPromise = new Promise((_, reject) => {
setTimeout(
() => reject(new Error(`Health check timeout after ${timeoutMs}ms`)),
timeoutMs
);
return {
healthy: true,
database: result.rows[0].database,
timestamp: result.rows[0].time,
pool: {
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
},
cache: {
size: queryCache.size,
maxSize: QUERY_CACHE_MAX_SIZE,
},
};
});
return Promise.race([healthPromise, timeoutPromise]);
};
// SAFEGUARD: Graceful pool shutdown for scripts/testing
const closePool = async () => {
try {
await pool.end();
logger.info("Database pool closed gracefully");
return true;
} catch (error) {
logger.error("Database health check failed:", error);
return {
healthy: false,
error: error.message,
};
logger.error("Error closing database pool:", error);
return false;
}
};
// SAFEGUARD: Get pool status for monitoring
const getPoolStatus = () => ({
total: pool.totalCount,
idle: pool.idleCount,
waiting: pool.waitingCount,
connected: poolConnected,
cacheSize: queryCache.size,
});
module.exports = {
pool,
query,
@@ -176,4 +292,6 @@ module.exports = {
batchQuery,
clearQueryCache,
healthCheck,
closePool,
getPoolStatus,
};

77
backend/scripts/db-health.js Executable file
View File

@@ -0,0 +1,77 @@
#!/usr/bin/env node
/**
* Database Health Check Script
* Tests database connectivity and performance
* Usage: node scripts/db-health.js
*/
const db = require("../config/database");
const logger = require("../config/logger");
async function runHealthCheck() {
console.log("🔍 Running database health check...\n");
try {
// Run health check with timeout
const result = await db.healthCheck(5000);
if (result.healthy) {
console.log("✅ DATABASE HEALTHY");
console.log("━━━━━━━━━━━━━━━━━━━━━━");
console.log(`Database: ${result.database}`);
console.log(`Timestamp: ${result.timestamp}`);
console.log(`\nConnection Pool:`);
console.log(` Total Connections: ${result.pool.total}`);
console.log(` Idle Connections: ${result.pool.idle}`);
console.log(` Waiting Requests: ${result.pool.waiting}`);
console.log(` Pool Connected: ${result.pool.connected ? "✓" : "✗"}`);
console.log(`\nQuery Cache:`);
console.log(
` Cached Queries: ${result.cache.size}/${result.cache.maxSize}`
);
console.log(
` Usage: ${((result.cache.size / result.cache.maxSize) * 100).toFixed(
1
)}%`
);
// Get additional pool status
const poolStatus = db.getPoolStatus();
console.log(`\n📊 Pool Status: OPERATIONAL`);
process.exitCode = 0;
} else {
console.log("❌ DATABASE UNHEALTHY");
console.log("━━━━━━━━━━━━━━━━━━━━━━");
console.log(`Error: ${result.error}`);
if (result.pool) {
console.log(`\nPool State:`);
console.log(
` Total: ${result.pool.total}, Idle: ${result.pool.idle}, Waiting: ${result.pool.waiting}`
);
}
process.exitCode = 1;
}
} catch (error) {
console.log("💥 HEALTH CHECK FAILED");
console.log("━━━━━━━━━━━━━━━━━━━━━━");
console.log(`Error: ${error.message}`);
console.log(`\nThis usually indicates:`);
console.log(` 1. Database connection timeout`);
console.log(` 2. PostgreSQL service not running`);
console.log(` 3. Network/firewall issues`);
console.log(` 4. Database credentials incorrect`);
process.exitCode = 1;
} finally {
// Gracefully close the pool
console.log("\n🔌 Closing database connections...");
await db.closePool();
console.log("✓ Database pool closed\n");
}
}
// Run the check
runHealthCheck().catch((err) => {
console.error("Fatal error:", err);
process.exit(1);
});

19
backend/test_db_quick.js Normal file
View File

@@ -0,0 +1,19 @@
const {query} = require('./config/database');
console.log('Testing query wrapper...');
const timeout = setTimeout(() => {
console.log('TIMEOUT - query() is hanging!');
process.exit(1);
}, 3000);
query('SELECT NOW() as time')
.then(r => {
clearTimeout(timeout);
console.log('SUCCESS:', r.rows[0]);
process.exit(0);
})
.catch(e => {
clearTimeout(timeout);
console.log('ERROR:', e.message);
process.exit(1);
});

View File

@@ -0,0 +1,24 @@
const db = require('./config/database');
console.log('Testing healthCheck...');
const timeout = setTimeout(() => {
console.log('TIMEOUT - healthCheck() is hanging!');
console.log('Pool stats:', {
total: db.pool.totalCount,
idle: db.pool.idleCount,
waiting: db.pool.waitingCount
});
process.exit(1);
}, 5000);
db.healthCheck()
.then(result => {
clearTimeout(timeout);
console.log('SUCCESS:', JSON.stringify(result, null, 2));
process.exit(0);
})
.catch(e => {
clearTimeout(timeout);
console.log('ERROR:', e.message);
process.exit(1);
});