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