mirror of
https://github.com/9technologygroup/patchmon.net.git
synced 2025-11-04 14:03:17 +00:00
improved table views and added more host information
This commit is contained in:
80
backend/src/config/database.js
Normal file
80
backend/src/config/database.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Database configuration for multiple instances
|
||||
* Optimizes connection pooling to prevent "too many connections" errors
|
||||
*/
|
||||
|
||||
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']
|
||||
: ['warn', 'error'],
|
||||
errorFormat: 'pretty'
|
||||
});
|
||||
}
|
||||
|
||||
// Connection health check
|
||||
async function checkDatabaseConnection(prisma) {
|
||||
try {
|
||||
await prisma.$queryRaw`SELECT 1`;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Database connection failed:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful disconnect with retry
|
||||
async function disconnectPrisma(prisma, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
await prisma.$disconnect();
|
||||
console.log('Database disconnected successfully');
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error(`Disconnect attempt ${i + 1} failed:`, error.message);
|
||||
if (i === maxRetries - 1) {
|
||||
console.error('Failed to disconnect from database after all retries');
|
||||
} else {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPrismaClient,
|
||||
checkDatabaseConnection,
|
||||
disconnectPrisma,
|
||||
getOptimizedDatabaseUrl
|
||||
};
|
||||
@@ -162,6 +162,7 @@ router.get('/hosts', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
// Show all hosts regardless of status
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
@@ -200,6 +201,13 @@ router.get('/hosts', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Get total packages count for this host
|
||||
const totalPackagesCount = await prisma.hostPackage.count({
|
||||
where: {
|
||||
hostId: host.id
|
||||
}
|
||||
});
|
||||
|
||||
// Get the agent update interval setting for stale calculation
|
||||
const settings = await prisma.settings.findFirst();
|
||||
const updateIntervalMinutes = settings?.updateInterval || 60;
|
||||
@@ -217,6 +225,7 @@ router.get('/hosts', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
return {
|
||||
...host,
|
||||
updatesCount,
|
||||
totalPackagesCount,
|
||||
isStale,
|
||||
effectiveStatus
|
||||
};
|
||||
@@ -256,7 +265,7 @@ router.get('/packages', authenticateToken, requireViewPackages, async (req, res)
|
||||
host: {
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true,
|
||||
friendlyName: true,
|
||||
osType: true
|
||||
}
|
||||
}
|
||||
@@ -278,7 +287,7 @@ router.get('/packages', authenticateToken, requireViewPackages, async (req, res)
|
||||
isSecurityUpdate: pkg.hostPackages.some(hp => hp.isSecurityUpdate),
|
||||
affectedHosts: pkg.hostPackages.map(hp => ({
|
||||
hostId: hp.host.id,
|
||||
hostname: hp.host.hostname,
|
||||
friendlyName: hp.host.friendlyName,
|
||||
osType: hp.host.osType,
|
||||
currentVersion: hp.currentVersion,
|
||||
availableVersion: hp.availableVersion,
|
||||
|
||||
@@ -41,6 +41,7 @@ router.get('/:id', authenticateToken, async (req, res) => {
|
||||
hosts: {
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
@@ -201,7 +202,7 @@ router.get('/:id/hosts', authenticateToken, async (req, res) => {
|
||||
where: { hostGroupId: id },
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true,
|
||||
friendlyName: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
osVersion: true,
|
||||
@@ -211,7 +212,7 @@ router.get('/:id/hosts', authenticateToken, async (req, res) => {
|
||||
createdAt: true
|
||||
},
|
||||
orderBy: {
|
||||
hostname: 'asc'
|
||||
friendlyName: 'asc'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ const validateApiCredentials = async (req, res, next) => {
|
||||
|
||||
// Admin endpoint to create a new host manually (replaces auto-registration)
|
||||
router.post('/create', authenticateToken, requireManageHosts, [
|
||||
body('hostname').isLength({ min: 1 }).withMessage('Hostname is required'),
|
||||
body('friendlyName').isLength({ min: 1 }).withMessage('Friendly name is required'),
|
||||
body('hostGroupId').optional()
|
||||
], async (req, res) => {
|
||||
try {
|
||||
@@ -142,14 +142,14 @@ router.post('/create', authenticateToken, requireManageHosts, [
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { hostname, hostGroupId } = req.body;
|
||||
const { friendlyName, hostGroupId } = req.body;
|
||||
|
||||
// Generate unique API credentials for this host
|
||||
const { apiId, apiKey } = generateApiCredentials();
|
||||
|
||||
// Check if host already exists
|
||||
const existingHost = await prisma.host.findUnique({
|
||||
where: { hostname }
|
||||
where: { friendlyName }
|
||||
});
|
||||
|
||||
if (existingHost) {
|
||||
@@ -170,7 +170,7 @@ router.post('/create', authenticateToken, requireManageHosts, [
|
||||
// Create new host with API credentials - system info will be populated when agent connects
|
||||
const host = await prisma.host.create({
|
||||
data: {
|
||||
hostname,
|
||||
friendlyName,
|
||||
osType: 'unknown', // Will be updated when agent connects
|
||||
osVersion: 'unknown', // Will be updated when agent connects
|
||||
ip: null, // Will be updated when agent connects
|
||||
@@ -194,7 +194,7 @@ router.post('/create', authenticateToken, requireManageHosts, [
|
||||
res.status(201).json({
|
||||
message: 'Host created successfully',
|
||||
hostId: host.id,
|
||||
hostname: host.hostname,
|
||||
friendlyName: host.friendlyName,
|
||||
apiId: host.apiId,
|
||||
apiKey: host.apiKey,
|
||||
hostGroup: host.hostGroup,
|
||||
@@ -223,7 +223,22 @@ router.post('/update', validateApiCredentials, [
|
||||
body('packages.*.availableVersion').optional().isLength({ min: 1 }),
|
||||
body('packages.*.needsUpdate').isBoolean().withMessage('needsUpdate must be boolean'),
|
||||
body('packages.*.isSecurityUpdate').optional().isBoolean().withMessage('isSecurityUpdate must be boolean'),
|
||||
body('agentVersion').optional().isLength({ min: 1 }).withMessage('Agent version must be a non-empty string')
|
||||
body('agentVersion').optional().isLength({ min: 1 }).withMessage('Agent version must be a non-empty string'),
|
||||
// Hardware Information
|
||||
body('cpuModel').optional().isString().withMessage('CPU model must be a string'),
|
||||
body('cpuCores').optional().isInt({ min: 1 }).withMessage('CPU cores must be a positive integer'),
|
||||
body('ramInstalled').optional().isInt({ min: 1 }).withMessage('RAM installed must be a positive integer'),
|
||||
body('swapSize').optional().isInt({ min: 0 }).withMessage('Swap size must be a non-negative integer'),
|
||||
body('diskDetails').optional().isArray().withMessage('Disk details must be an array'),
|
||||
// Network Information
|
||||
body('gatewayIp').optional().isIP().withMessage('Gateway IP must be a valid IP address'),
|
||||
body('dnsServers').optional().isArray().withMessage('DNS servers must be an array'),
|
||||
body('networkInterfaces').optional().isArray().withMessage('Network interfaces must be an array'),
|
||||
// System Information
|
||||
body('kernelVersion').optional().isString().withMessage('Kernel version must be a string'),
|
||||
body('selinuxStatus').optional().isIn(['enabled', 'disabled', 'permissive']).withMessage('SELinux status must be enabled, disabled, or permissive'),
|
||||
body('systemUptime').optional().isString().withMessage('System uptime must be a string'),
|
||||
body('loadAverage').optional().isArray().withMessage('Load average must be an array')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
@@ -234,14 +249,35 @@ router.post('/update', validateApiCredentials, [
|
||||
const { packages, repositories } = req.body;
|
||||
const host = req.hostRecord;
|
||||
|
||||
// Update host last update timestamp and OS info if provided
|
||||
// Update host last update timestamp and system info if provided
|
||||
const updateData = { lastUpdate: new Date() };
|
||||
|
||||
// Basic system info
|
||||
if (req.body.osType) updateData.osType = req.body.osType;
|
||||
if (req.body.osVersion) updateData.osVersion = req.body.osVersion;
|
||||
if (req.body.hostname) updateData.hostname = req.body.hostname;
|
||||
if (req.body.ip) updateData.ip = req.body.ip;
|
||||
if (req.body.architecture) updateData.architecture = req.body.architecture;
|
||||
if (req.body.agentVersion) updateData.agentVersion = req.body.agentVersion;
|
||||
|
||||
// Hardware Information
|
||||
if (req.body.cpuModel) updateData.cpuModel = req.body.cpuModel;
|
||||
if (req.body.cpuCores) updateData.cpuCores = req.body.cpuCores;
|
||||
if (req.body.ramInstalled) updateData.ramInstalled = req.body.ramInstalled;
|
||||
if (req.body.swapSize !== undefined) updateData.swapSize = req.body.swapSize;
|
||||
if (req.body.diskDetails) updateData.diskDetails = req.body.diskDetails;
|
||||
|
||||
// Network Information
|
||||
if (req.body.gatewayIp) updateData.gatewayIp = req.body.gatewayIp;
|
||||
if (req.body.dnsServers) updateData.dnsServers = req.body.dnsServers;
|
||||
if (req.body.networkInterfaces) updateData.networkInterfaces = req.body.networkInterfaces;
|
||||
|
||||
// System Information
|
||||
if (req.body.kernelVersion) updateData.kernelVersion = req.body.kernelVersion;
|
||||
if (req.body.selinuxStatus) updateData.selinuxStatus = req.body.selinuxStatus;
|
||||
if (req.body.systemUptime) updateData.systemUptime = req.body.systemUptime;
|
||||
if (req.body.loadAverage) updateData.loadAverage = req.body.loadAverage;
|
||||
|
||||
// If this is the first update (status is 'pending'), change to 'active'
|
||||
if (host.status === 'pending') {
|
||||
updateData.status = 'active';
|
||||
@@ -454,6 +490,7 @@ router.get('/info', validateApiCredentials, async (req, res) => {
|
||||
where: { id: req.hostRecord.id },
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
@@ -485,12 +522,12 @@ router.post('/ping', validateApiCredentials, async (req, res) => {
|
||||
const response = {
|
||||
message: 'Ping successful',
|
||||
timestamp: new Date().toISOString(),
|
||||
hostname: req.hostRecord.hostname
|
||||
friendlyName: req.hostRecord.friendlyName
|
||||
};
|
||||
|
||||
// Check if this is a crontab update trigger
|
||||
if (req.body.triggerCrontabUpdate && req.hostRecord.autoUpdate) {
|
||||
console.log(`Triggering crontab update for host: ${req.hostRecord.hostname}`);
|
||||
console.log(`Triggering crontab update for host: ${req.hostRecord.friendlyName}`);
|
||||
response.crontabUpdate = {
|
||||
shouldUpdate: true,
|
||||
message: 'Update interval changed, please run: /usr/local/bin/patchmon-agent.sh update-crontab',
|
||||
@@ -568,7 +605,7 @@ router.put('/bulk/group', authenticateToken, requireManageHosts, [
|
||||
// Check if all hosts exist
|
||||
const existingHosts = await prisma.host.findMany({
|
||||
where: { id: { in: hostIds } },
|
||||
select: { id: true, hostname: true }
|
||||
select: { id: true, friendlyName: true }
|
||||
});
|
||||
|
||||
if (existingHosts.length !== hostIds.length) {
|
||||
@@ -593,7 +630,7 @@ router.put('/bulk/group', authenticateToken, requireManageHosts, [
|
||||
where: { id: { in: hostIds } },
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true,
|
||||
friendlyName: true,
|
||||
hostGroup: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -681,6 +718,7 @@ router.get('/admin/list', authenticateToken, requireManageHosts, async (req, res
|
||||
const hosts = await prisma.host.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
@@ -742,7 +780,7 @@ router.patch('/:hostId/auto-update', authenticateToken, requireManageHosts, [
|
||||
message: `Host auto-update ${autoUpdate ? 'enabled' : 'disabled'} successfully`,
|
||||
host: {
|
||||
id: host.id,
|
||||
hostname: host.hostname,
|
||||
friendlyName: host.friendlyName,
|
||||
autoUpdate: host.autoUpdate
|
||||
}
|
||||
});
|
||||
@@ -934,4 +972,77 @@ router.delete('/agent/versions/:versionId', authenticateToken, requireManageSett
|
||||
}
|
||||
});
|
||||
|
||||
// Update host friendly name (admin only)
|
||||
router.patch('/:hostId/friendly-name', authenticateToken, requireManageHosts, [
|
||||
body('friendlyName').isLength({ min: 1, max: 100 }).withMessage('Friendly name must be between 1 and 100 characters')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({ errors: errors.array() });
|
||||
}
|
||||
|
||||
const { hostId } = req.params;
|
||||
const { friendlyName } = req.body;
|
||||
|
||||
// Check if host exists
|
||||
const host = await prisma.host.findUnique({
|
||||
where: { id: hostId }
|
||||
});
|
||||
|
||||
if (!host) {
|
||||
return res.status(404).json({ error: 'Host not found' });
|
||||
}
|
||||
|
||||
// Check if friendly name is already taken by another host
|
||||
const existingHost = await prisma.host.findFirst({
|
||||
where: {
|
||||
friendlyName: friendlyName,
|
||||
id: { not: hostId }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingHost) {
|
||||
return res.status(400).json({ error: 'Friendly name is already taken by another host' });
|
||||
}
|
||||
|
||||
// Update the friendly name
|
||||
const updatedHost = await prisma.host.update({
|
||||
where: { id: hostId },
|
||||
data: { friendlyName },
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
osVersion: true,
|
||||
architecture: true,
|
||||
lastUpdate: true,
|
||||
status: true,
|
||||
hostGroupId: true,
|
||||
agentVersion: true,
|
||||
autoUpdate: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
hostGroup: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
color: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: 'Friendly name updated successfully',
|
||||
host: updatedHost
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Update friendly name error:', error);
|
||||
res.status(500).json({ error: 'Failed to update friendly name' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -100,6 +100,7 @@ router.get('/', async (req, res) => {
|
||||
host: {
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
osType: true
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ router.get('/', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
host: {
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true,
|
||||
friendlyName: true,
|
||||
status: true
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ router.get('/', authenticateToken, requireViewHosts, async (req, res) => {
|
||||
activeHostCount: repo.hostRepositories.filter(hr => hr.host.status === 'active').length,
|
||||
hosts: repo.hostRepositories.map(hr => ({
|
||||
id: hr.host.id,
|
||||
hostname: hr.host.hostname,
|
||||
friendlyName: hr.host.friendlyName,
|
||||
status: hr.host.status,
|
||||
isEnabled: hr.isEnabled,
|
||||
lastChecked: hr.lastChecked
|
||||
@@ -69,7 +69,7 @@ router.get('/host/:hostId', authenticateToken, requireViewHosts, async (req, res
|
||||
host: {
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true
|
||||
friendlyName: true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -100,6 +100,7 @@ router.get('/:repositoryId', authenticateToken, requireViewHosts, async (req, re
|
||||
host: {
|
||||
select: {
|
||||
id: true,
|
||||
friendlyName: true,
|
||||
hostname: true,
|
||||
ip: true,
|
||||
osType: true,
|
||||
@@ -111,7 +112,7 @@ router.get('/:repositoryId', authenticateToken, requireViewHosts, async (req, re
|
||||
},
|
||||
orderBy: {
|
||||
host: {
|
||||
hostname: 'asc'
|
||||
friendlyName: 'asc'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,14 +198,14 @@ router.patch('/host/:hostId/repository/:repositoryId', authenticateToken, requir
|
||||
repository: true,
|
||||
host: {
|
||||
select: {
|
||||
hostname: true
|
||||
friendlyName: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: `Repository ${isEnabled ? 'enabled' : 'disabled'} for host ${hostRepository.host.hostname}`,
|
||||
message: `Repository ${isEnabled ? 'enabled' : 'disabled'} for host ${hostRepository.host.friendlyName}`,
|
||||
hostRepository
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -20,7 +20,7 @@ async function triggerCrontabUpdates() {
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
hostname: true,
|
||||
friendlyName: true,
|
||||
apiId: true,
|
||||
apiKey: true
|
||||
}
|
||||
@@ -32,7 +32,7 @@ async function triggerCrontabUpdates() {
|
||||
// This is done by sending a ping with a special flag
|
||||
for (const host of hosts) {
|
||||
try {
|
||||
console.log(`Triggering crontab update for host: ${host.hostname}`);
|
||||
console.log(`Triggering crontab update for host: ${host.friendlyName}`);
|
||||
|
||||
// We'll use the existing ping endpoint but add a special parameter
|
||||
// The agent will detect this and run update-crontab command
|
||||
@@ -64,20 +64,20 @@ async function triggerCrontabUpdates() {
|
||||
|
||||
const req = client.request(options, (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log(`Successfully triggered crontab update for ${host.hostname}`);
|
||||
console.log(`Successfully triggered crontab update for ${host.friendlyName}`);
|
||||
} else {
|
||||
console.error(`Failed to trigger crontab update for ${host.hostname}: ${res.statusCode}`);
|
||||
console.error(`Failed to trigger crontab update for ${host.friendlyName}: ${res.statusCode}`);
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
console.error(`Error triggering crontab update for ${host.hostname}:`, error.message);
|
||||
console.error(`Error triggering crontab update for ${host.friendlyName}:`, error.message);
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
} catch (error) {
|
||||
console.error(`Error triggering crontab update for ${host.hostname}:`, error.message);
|
||||
console.error(`Error triggering crontab update for ${host.friendlyName}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { createPrismaClient, checkDatabaseConnection, disconnectPrisma } = require('./config/database');
|
||||
const winston = require('winston');
|
||||
|
||||
// Import routes
|
||||
@@ -20,8 +20,8 @@ const versionRoutes = require('./routes/versionRoutes');
|
||||
const tfaRoutes = require('./routes/tfaRoutes');
|
||||
const updateScheduler = require('./services/updateScheduler');
|
||||
|
||||
// Initialize Prisma client
|
||||
const prisma = new PrismaClient();
|
||||
// Initialize Prisma client with optimized connection pooling for multiple instances
|
||||
const prisma = createPrismaClient();
|
||||
|
||||
// Initialize logger - only if logging is enabled
|
||||
const logger = process.env.ENABLE_LOGGING === 'true' ? winston.createLogger({
|
||||
@@ -157,33 +157,53 @@ app.use('*', (req, res) => {
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
if (process.env.ENABLE_LOGGING === 'true') {
|
||||
logger.info('SIGTERM received, shutting down gracefully');
|
||||
}
|
||||
updateScheduler.stop();
|
||||
await prisma.$disconnect();
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
if (process.env.ENABLE_LOGGING === 'true') {
|
||||
logger.info('SIGINT received, shutting down gracefully');
|
||||
}
|
||||
updateScheduler.stop();
|
||||
await prisma.$disconnect();
|
||||
await disconnectPrisma(prisma);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// Start server
|
||||
app.listen(PORT, () => {
|
||||
process.on('SIGTERM', async () => {
|
||||
if (process.env.ENABLE_LOGGING === 'true') {
|
||||
logger.info(`Server running on port ${PORT}`);
|
||||
logger.info(`Environment: ${process.env.NODE_ENV}`);
|
||||
logger.info('SIGTERM received, shutting down gracefully');
|
||||
}
|
||||
|
||||
// Start update scheduler
|
||||
updateScheduler.start();
|
||||
updateScheduler.stop();
|
||||
await disconnectPrisma(prisma);
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (process.env.ENABLE_LOGGING === 'true') {
|
||||
logger.info('✅ Database connection successful');
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to start server:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
startServer();
|
||||
|
||||
module.exports = app;
|
||||
Reference in New Issue
Block a user