'use strict'; /** * Simple Health Check Controller * Provides basic health monitoring for partner integration */ const { PartnerSystemUser } = require('../model/partner'); const partnerSyncService = require('../services/partner_sync_service'); const mongoose = require('mongoose'); const { SyncStatus, HealthStatus } = require('../helpers/constants'); const env = require('../helpers/env'); const { getQueueStats, getDLQConnection, closeConnection } = require('../helpers/dlq_queue_setup'); /** * Check DLQ (Dead Letter Queue) health for multiple queues * @param {Array} queueNames - Array of queue names to check (defaults to partner_tasks) */ async function checkDLQHealth(queueNames = [env.QUEUE_NAME_PARTNER]) { let connection, channel; try { // Connect to RabbitMQ using helper connection = await getDLQConnection(); channel = await connection.createChannel(); const threshold = env.DLQ_ALERT_THRESHOLD; const critical = env.DLQ_ALERT_CRITICAL; // Check all queues const queues = {}; let overallMessageCount = 0; let worstStatus = HealthStatus.HEALTHY; for (const queueName of queueNames) { const dlqName = `${queueName}_dlq`; try { const stats = await getQueueStats(channel, dlqName); const messageCount = stats.messageCount; overallMessageCount += messageCount; // Determine status for this queue let status = HealthStatus.HEALTHY; let message = 'Operating normally'; if (messageCount >= critical) { status = HealthStatus.UNHEALTHY; message = `Critical: ${messageCount} messages (threshold: ${critical})`; } else if (messageCount >= threshold) { status = HealthStatus.DEGRADED; message = `Warning: ${messageCount} messages (threshold: ${threshold})`; } // Track worst status if (status === HealthStatus.UNHEALTHY) { worstStatus = HealthStatus.UNHEALTHY; } else if (status === HealthStatus.DEGRADED && worstStatus === HealthStatus.HEALTHY) { worstStatus = HealthStatus.DEGRADED; } queues[queueName] = { status, message, dlqName, messageCount, consumerCount: stats.consumerCount }; } catch (error) { queues[queueName] = { status: HealthStatus.UNHEALTHY, error: error.message, message: 'Unable to check DLQ' }; worstStatus = HealthStatus.UNHEALTHY; } } // Overall summary let overallMessage = 'All DLQs operating normally'; if (worstStatus === HealthStatus.UNHEALTHY) { overallMessage = `Critical: ${overallMessageCount} total messages across ${queueNames.length} queue(s)`; } else if (worstStatus === HealthStatus.DEGRADED) { overallMessage = `Warning: ${overallMessageCount} total messages across ${queueNames.length} queue(s)`; } return { status: worstStatus, message: overallMessage, totalMessages: overallMessageCount, threshold, critical, consumerEnabled: env.DLQ_CONSUMER_ENABLED, retentionDays: env.DLQ_RETENTION_DAYS, queues }; } catch (error) { return { status: HealthStatus.UNHEALTHY, error: error.message, message: 'Unable to connect to RabbitMQ' }; } finally { await closeConnection(connection, channel); } } /** * Basic health check endpoint */ async function healthCheck_get(req, res) { try { const health = { timestamp: new Date(), overall: HealthStatus.HEALTHY, components: {} }; // Check database connectivity try { const dbState = mongoose.connection.readyState; const dbStatus = dbState === 1 ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY; health.components.database = { status: dbStatus, state: dbState, message: dbState === 1 ? 'Connected' : 'Disconnected' }; } catch (error) { health.components.database = { status: HealthStatus.UNHEALTHY, error: error.message }; health.overall = HealthStatus.UNHEALTHY; } // Check partner system users try { const activeUsers = await PartnerSystemUser.countDocuments({ active: true }); const errorUsers = await PartnerSystemUser.countDocuments({ syncStatus: SyncStatus.ERROR }); const errorRate = activeUsers > 0 ? (errorUsers / activeUsers * 100) : 0; const status = errorRate > 50 ? HealthStatus.DEGRADED : HealthStatus.HEALTHY; health.components.partnerUsers = { status, activeUsers, errorUsers, errorRate: Math.round(errorRate * 100) / 100 }; if (status === HealthStatus.DEGRADED) { health.overall = HealthStatus.DEGRADED; } } catch (error) { health.components.partnerUsers = { status: HealthStatus.UNHEALTHY, error: error.message }; health.overall = HealthStatus.UNHEALTHY; } // Check partner services try { const partnerHealth = await partnerSyncService.healthCheck(); health.components.partnerServices = partnerHealth; if (partnerHealth.overall !== HealthStatus.HEALTHY) { health.overall = partnerHealth.overall; } } catch (error) { health.components.partnerServices = { status: HealthStatus.UNHEALTHY, error: error.message }; health.overall = HealthStatus.UNHEALTHY; } // Check application health const memoryUsage = process.memoryUsage(); const uptime = process.uptime(); const memoryThreshold = 1024 * 1024 * 1024; // 1GB health.components.application = { status: memoryUsage.heapUsed < memoryThreshold ? HealthStatus.HEALTHY : HealthStatus.DEGRADED, memoryUsedMB: Math.round(memoryUsage.heapUsed / 1024 / 1024), uptimeHours: Math.round(uptime / 3600 * 100) / 100, nodeVersion: process.version }; if (health.components.application.status === HealthStatus.DEGRADED && health.overall === HealthStatus.HEALTHY) { health.overall = HealthStatus.DEGRADED; } // Check DLQ (Dead Letter Queue) health try { const dlqHealth = await checkDLQHealth(); health.components.dlq = dlqHealth; if (dlqHealth.status === HealthStatus.UNHEALTHY) { health.overall = HealthStatus.UNHEALTHY; } else if (dlqHealth.status === HealthStatus.DEGRADED && health.overall === HealthStatus.HEALTHY) { health.overall = HealthStatus.DEGRADED; } } catch (error) { health.components.dlq = { status: HealthStatus.UNHEALTHY, error: error.message }; // Don't fail overall health if DLQ check fails } // Set HTTP status code based on health const statusCode = health.overall === HealthStatus.HEALTHY ? 200 : health.overall === HealthStatus.DEGRADED ? 200 : 503; res.status(statusCode).json(health); } catch (error) { res.status(503).json({ timestamp: new Date(), overall: HealthStatus.UNHEALTHY, error: error.message }); } } /** * Partner statistics endpoint */ async function partnerStats_get(req, res) { try { const stats = { timestamp: new Date(), partners: {}, summary: {} }; // Get partner statistics const partnerStats = await PartnerSystemUser.aggregate([ { $lookup: { from: 'users', localField: 'partner', foreignField: '_id', as: 'partner' } }, { $unwind: '$partner' }, { $group: { _id: '$partner.partnerCode', totalUsers: { $sum: 1 }, activeUsers: { $sum: { $cond: [{ $eq: ['$active', true] }, 1, 0] } }, errorUsers: { $sum: { $cond: [{ $eq: ['$syncStatus', SyncStatus.ERROR] }, 1, 0] } }, lastSyncUsers: { $sum: { $cond: [ { $gte: [ '$lastSyncAt', new Date(Date.now() - 24 * 60 * 60 * 1000) // Last 24 hours ] }, 1, 0 ] } } } } ]); // Process partner statistics for (const partnerStat of partnerStats) { stats.partners[partnerStat._id] = { totalUsers: partnerStat.totalUsers, activeUsers: partnerStat.activeUsers, errorUsers: partnerStat.errorUsers, lastSyncUsers: partnerStat.lastSyncUsers, errorRate: partnerStat.activeUsers > 0 ? Math.round((partnerStat.errorUsers / partnerStat.activeUsers) * 100 * 100) / 100 : 0 }; } // Calculate summary stats.summary = { totalPartners: Object.keys(stats.partners).length, totalPartnerUsers: partnerStats.reduce((sum, p) => sum + p.totalUsers, 0), activePartnerUsers: partnerStats.reduce((sum, p) => sum + p.activeUsers, 0), recentErrors: partnerStats.reduce((sum, p) => sum + p.errorUsers, 0), availableServices: partnerSyncService.getAvailablePartners() }; res.json(stats); } catch (error) { res.status(500).json({ error: error.message, timestamp: new Date() }); } } module.exports = { healthCheck_get, partnerStats_get };