mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-08 07:52:21 +00:00
feat(backend): wait for DB on start
This commit is contained in:
@@ -8,36 +8,36 @@ const { PrismaClient } = require('@prisma/client');
|
|||||||
// Parse DATABASE_URL and add connection pooling parameters
|
// Parse DATABASE_URL and add connection pooling parameters
|
||||||
function getOptimizedDatabaseUrl() {
|
function getOptimizedDatabaseUrl() {
|
||||||
const originalUrl = process.env.DATABASE_URL;
|
const originalUrl = process.env.DATABASE_URL;
|
||||||
|
|
||||||
if (!originalUrl) {
|
if (!originalUrl) {
|
||||||
throw new Error('DATABASE_URL environment variable is required');
|
throw new Error('DATABASE_URL environment variable is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the URL
|
// Parse the URL
|
||||||
const url = new URL(originalUrl);
|
const url = new URL(originalUrl);
|
||||||
|
|
||||||
// Add connection pooling parameters for multiple instances
|
// Add connection pooling parameters for multiple instances
|
||||||
url.searchParams.set('connection_limit', '5'); // Reduced from default 10
|
url.searchParams.set('connection_limit', '5'); // Reduced from default 10
|
||||||
url.searchParams.set('pool_timeout', '10'); // 10 seconds
|
url.searchParams.set('pool_timeout', '10'); // 10 seconds
|
||||||
url.searchParams.set('connect_timeout', '10'); // 10 seconds
|
url.searchParams.set('connect_timeout', '10'); // 10 seconds
|
||||||
url.searchParams.set('idle_timeout', '300'); // 5 minutes
|
url.searchParams.set('idle_timeout', '300'); // 5 minutes
|
||||||
url.searchParams.set('max_lifetime', '1800'); // 30 minutes
|
url.searchParams.set('max_lifetime', '1800'); // 30 minutes
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create optimized Prisma client
|
// Create optimized Prisma client
|
||||||
function createPrismaClient() {
|
function createPrismaClient() {
|
||||||
const optimizedUrl = getOptimizedDatabaseUrl();
|
const optimizedUrl = getOptimizedDatabaseUrl();
|
||||||
|
|
||||||
return new PrismaClient({
|
return new PrismaClient({
|
||||||
datasources: {
|
datasources: {
|
||||||
db: {
|
db: {
|
||||||
url: optimizedUrl
|
url: optimizedUrl
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
log: process.env.NODE_ENV === 'development'
|
log: process.env.NODE_ENV === 'development'
|
||||||
? ['query', 'info', 'warn', 'error']
|
? ['query', 'info', 'warn', 'error']
|
||||||
: ['warn', 'error'],
|
: ['warn', 'error'],
|
||||||
errorFormat: 'pretty'
|
errorFormat: 'pretty'
|
||||||
});
|
});
|
||||||
@@ -54,6 +54,33 @@ async function checkDatabaseConnection(prisma) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for database to be available with retry logic
|
||||||
|
async function waitForDatabase(prisma, options = {}) {
|
||||||
|
const maxAttempts = options.maxAttempts || parseInt(process.env.DB_MAX_ATTEMPTS) || 30;
|
||||||
|
const waitInterval = options.waitInterval || parseInt(process.env.DB_WAIT_INTERVAL) || 2;
|
||||||
|
|
||||||
|
console.log(`Waiting for database connection (max ${maxAttempts} attempts, ${waitInterval}s interval)...`);
|
||||||
|
|
||||||
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||||
|
try {
|
||||||
|
const isConnected = await checkDatabaseConnection(prisma);
|
||||||
|
if (isConnected) {
|
||||||
|
console.log(`Database connected successfully after ${attempt} attempt(s)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// checkDatabaseConnection already logs the error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt < maxAttempts) {
|
||||||
|
console.log(`⏳ Database not ready (attempt ${attempt}/${maxAttempts}), retrying in ${waitInterval}s...`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, waitInterval * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`❌ Database failed to become available after ${maxAttempts} attempts`);
|
||||||
|
}
|
||||||
|
|
||||||
// Graceful disconnect with retry
|
// Graceful disconnect with retry
|
||||||
async function disconnectPrisma(prisma, maxRetries = 3) {
|
async function disconnectPrisma(prisma, maxRetries = 3) {
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
@@ -75,6 +102,7 @@ async function disconnectPrisma(prisma, maxRetries = 3) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
createPrismaClient,
|
createPrismaClient,
|
||||||
checkDatabaseConnection,
|
checkDatabaseConnection,
|
||||||
|
waitForDatabase,
|
||||||
disconnectPrisma,
|
disconnectPrisma,
|
||||||
getOptimizedDatabaseUrl
|
getOptimizedDatabaseUrl
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const express = require('express');
|
|||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const rateLimit = require('express-rate-limit');
|
const rateLimit = require('express-rate-limit');
|
||||||
const { createPrismaClient, checkDatabaseConnection, disconnectPrisma } = require('./config/database');
|
const { createPrismaClient, waitForDatabase, disconnectPrisma } = require('./config/database');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
|
|
||||||
// Import routes
|
// Import routes
|
||||||
@@ -27,24 +27,24 @@ const prisma = createPrismaClient();
|
|||||||
function compareVersions(version1, version2) {
|
function compareVersions(version1, version2) {
|
||||||
const v1Parts = version1.split('.').map(Number);
|
const v1Parts = version1.split('.').map(Number);
|
||||||
const v2Parts = version2.split('.').map(Number);
|
const v2Parts = version2.split('.').map(Number);
|
||||||
|
|
||||||
// Ensure both arrays have the same length
|
// Ensure both arrays have the same length
|
||||||
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
||||||
while (v1Parts.length < maxLength) v1Parts.push(0);
|
while (v1Parts.length < maxLength) v1Parts.push(0);
|
||||||
while (v2Parts.length < maxLength) v2Parts.push(0);
|
while (v2Parts.length < maxLength) v2Parts.push(0);
|
||||||
|
|
||||||
for (let i = 0; i < maxLength; i++) {
|
for (let i = 0; i < maxLength; i++) {
|
||||||
if (v1Parts[i] > v2Parts[i]) return true;
|
if (v1Parts[i] > v2Parts[i]) return true;
|
||||||
if (v1Parts[i] < v2Parts[i]) return false;
|
if (v1Parts[i] < v2Parts[i]) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // versions are equal
|
return false; // versions are equal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check and import agent version on startup
|
// Function to check and import agent version on startup
|
||||||
async function checkAndImportAgentVersion() {
|
async function checkAndImportAgentVersion() {
|
||||||
console.log('🔍 Starting agent version auto-import check...');
|
console.log('🔍 Starting agent version auto-import check...');
|
||||||
|
|
||||||
// Skip if auto-import is disabled
|
// Skip if auto-import is disabled
|
||||||
if (process.env.AUTO_IMPORT_AGENT_VERSION === 'false') {
|
if (process.env.AUTO_IMPORT_AGENT_VERSION === 'false') {
|
||||||
console.log('❌ Auto-import of agent version is disabled');
|
console.log('❌ Auto-import of agent version is disabled');
|
||||||
@@ -53,16 +53,16 @@ async function checkAndImportAgentVersion() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
// Path to the agent script file
|
// Path to the agent script file
|
||||||
const agentScriptPath = path.join(__dirname, '../../agents/patchmon-agent.sh');
|
const agentScriptPath = path.join(__dirname, '../../agents/patchmon-agent.sh');
|
||||||
console.log('📁 Agent script path:', agentScriptPath);
|
console.log('📁 Agent script path:', agentScriptPath);
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
if (!fs.existsSync(agentScriptPath)) {
|
if (!fs.existsSync(agentScriptPath)) {
|
||||||
console.log('❌ Agent script file not found, skipping version check');
|
console.log('❌ Agent script file not found, skipping version check');
|
||||||
@@ -72,10 +72,10 @@ async function checkAndImportAgentVersion() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('✅ Agent script file found');
|
console.log('✅ Agent script file found');
|
||||||
|
|
||||||
// Read the file content
|
// Read the file content
|
||||||
const scriptContent = fs.readFileSync(agentScriptPath, 'utf8');
|
const scriptContent = fs.readFileSync(agentScriptPath, 'utf8');
|
||||||
|
|
||||||
// Extract version from script content
|
// Extract version from script content
|
||||||
const versionMatch = scriptContent.match(/AGENT_VERSION="([^"]+)"/);
|
const versionMatch = scriptContent.match(/AGENT_VERSION="([^"]+)"/);
|
||||||
if (!versionMatch) {
|
if (!versionMatch) {
|
||||||
@@ -85,15 +85,15 @@ async function checkAndImportAgentVersion() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localVersion = versionMatch[1];
|
const localVersion = versionMatch[1];
|
||||||
console.log('📋 Local version:', localVersion);
|
console.log('📋 Local version:', localVersion);
|
||||||
|
|
||||||
// Check if this version already exists in database
|
// Check if this version already exists in database
|
||||||
const existingVersion = await prisma.agent_versions.findUnique({
|
const existingVersion = await prisma.agent_versions.findUnique({
|
||||||
where: { version: localVersion }
|
where: { version: localVersion }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingVersion) {
|
if (existingVersion) {
|
||||||
console.log(`✅ Agent version ${localVersion} already exists in database`);
|
console.log(`✅ Agent version ${localVersion} already exists in database`);
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
@@ -102,21 +102,21 @@ async function checkAndImportAgentVersion() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`🆕 Agent version ${localVersion} not found in database`);
|
console.log(`🆕 Agent version ${localVersion} not found in database`);
|
||||||
|
|
||||||
// Check if there are any existing versions to compare with
|
// Check if there are any existing versions to compare with
|
||||||
const allVersions = await prisma.agent_versions.findMany({
|
const allVersions = await prisma.agent_versions.findMany({
|
||||||
select: { version: true },
|
select: { version: true },
|
||||||
orderBy: { created_at: 'desc' }
|
orderBy: { created_at: 'desc' }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (allVersions.length > 0) {
|
if (allVersions.length > 0) {
|
||||||
console.log(`📊 Found ${allVersions.length} existing versions in database`);
|
console.log(`📊 Found ${allVersions.length} existing versions in database`);
|
||||||
console.log(`📊 Latest version: ${allVersions[0].version}`);
|
console.log(`📊 Latest version: ${allVersions[0].version}`);
|
||||||
|
|
||||||
// Simple version comparison (assuming semantic versioning)
|
// Simple version comparison (assuming semantic versioning)
|
||||||
const isNewer = compareVersions(localVersion, allVersions[0].version);
|
const isNewer = compareVersions(localVersion, allVersions[0].version);
|
||||||
console.log(`🔄 Version comparison: ${localVersion} > ${allVersions[0].version} = ${isNewer}`);
|
console.log(`🔄 Version comparison: ${localVersion} > ${allVersions[0].version} = ${isNewer}`);
|
||||||
|
|
||||||
if (!isNewer) {
|
if (!isNewer) {
|
||||||
console.log(`❌ Agent version ${localVersion} is not newer than existing versions, skipping import`);
|
console.log(`❌ Agent version ${localVersion} is not newer than existing versions, skipping import`);
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
@@ -125,7 +125,7 @@ async function checkAndImportAgentVersion() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version doesn't exist, create it
|
// Version doesn't exist, create it
|
||||||
const agentVersion = await prisma.agent_versions.create({
|
const agentVersion = await prisma.agent_versions.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -150,12 +150,12 @@ async function checkAndImportAgentVersion() {
|
|||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`🎉 Successfully auto-imported new agent version ${localVersion} on startup`);
|
console.log(`🎉 Successfully auto-imported new agent version ${localVersion} on startup`);
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
logger.info(`✅ Auto-imported new agent version ${localVersion} on startup`);
|
logger.info(`✅ Auto-imported new agent version ${localVersion} on startup`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to check/import agent version on startup:', error.message);
|
console.error('❌ Failed to check/import agent version on startup:', error.message);
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
@@ -308,9 +308,9 @@ app.use((err, req, res, next) => {
|
|||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
logger.error(err.stack);
|
logger.error(err.stack);
|
||||||
}
|
}
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
error: 'Something went wrong!',
|
error: 'Something went wrong!',
|
||||||
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,26 +341,22 @@ process.on('SIGTERM', async () => {
|
|||||||
// Start server with database health check
|
// Start server with database health check
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
try {
|
||||||
// Check database connection before starting server
|
// Wait for database to be available
|
||||||
const isConnected = await checkDatabaseConnection(prisma);
|
await waitForDatabase(prisma);
|
||||||
if (!isConnected) {
|
|
||||||
console.error('❌ Database connection failed. Server not started.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
logger.info('✅ Database connection successful');
|
logger.info('✅ Database connection successful');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and import agent version on startup
|
// Check and import agent version on startup
|
||||||
await checkAndImportAgentVersion();
|
await checkAndImportAgentVersion();
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
if (process.env.ENABLE_LOGGING === 'true') {
|
if (process.env.ENABLE_LOGGING === 'true') {
|
||||||
logger.info(`Server running on port ${PORT}`);
|
logger.info(`Server running on port ${PORT}`);
|
||||||
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start update scheduler
|
// Start update scheduler
|
||||||
updateScheduler.start();
|
updateScheduler.start();
|
||||||
});
|
});
|
||||||
@@ -372,4 +368,4 @@ async function startServer() {
|
|||||||
|
|
||||||
startServer();
|
startServer();
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
|
|||||||
Reference in New Issue
Block a user