import { getNocoDbConfiguration } from '../utils/nocodb'; import { getMinioClient } from '../utils/minio'; import { useDirectus } from '#imports'; interface HealthCheckResult { status: 'healthy' | 'unhealthy' | 'degraded'; timestamp: string; uptime: number; services: { [key: string]: { status: 'up' | 'down' | 'slow'; responseTime?: number; error?: string; }; }; memory: { used: number; total: number; percentage: number; }; environment: { nodeVersion: string; configStatus: 'complete' | 'partial' | 'missing'; missingVars?: string[]; }; } export default defineEventHandler(async (event): Promise => { const startTime = Date.now(); const result: HealthCheckResult = { status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), services: {}, memory: { used: 0, total: 0, percentage: 0 }, environment: { nodeVersion: process.version, configStatus: 'complete' } }; // Memory check const memUsage = process.memoryUsage(); result.memory = { used: Math.round(memUsage.heapUsed / 1024 / 1024), // MB total: Math.round(memUsage.heapTotal / 1024 / 1024), // MB percentage: Math.round((memUsage.heapUsed / memUsage.heapTotal) * 100) }; // Check if memory usage is too high if (result.memory.percentage > 90) { result.status = 'unhealthy'; } else if (result.memory.percentage > 80) { result.status = 'degraded'; } // Environment variables check const requiredEnvVars = [ 'NUXT_NOCODB_URL', 'NUXT_NOCODB_TOKEN', 'NUXT_MINIO_ACCESS_KEY', 'NUXT_MINIO_SECRET_KEY', 'NUXT_DOCUMENSO_API_KEY', 'NUXT_DOCUMENSO_BASE_URL' ]; const missingVars = requiredEnvVars.filter(varName => !process.env[varName]); if (missingVars.length > 0) { result.environment.configStatus = missingVars.length > 3 ? 'missing' : 'partial'; result.environment.missingVars = missingVars; result.status = 'unhealthy'; } // Service health checks // 1. NocoDB Check try { const nocoStart = Date.now(); const nocoConfig = getNocoDbConfiguration(); if (!nocoConfig.url || !nocoConfig.token) { result.services.nocodb = { status: 'down', error: 'Missing configuration' }; result.status = 'unhealthy'; } else { const response = await fetch(`${nocoConfig.url}/api/v1/health`, { headers: { 'xc-token': nocoConfig.token }, signal: AbortSignal.timeout(5000) // 5 second timeout }); const responseTime = Date.now() - nocoStart; if (response.ok) { result.services.nocodb = { status: responseTime > 3000 ? 'slow' : 'up', responseTime }; if (responseTime > 3000) result.status = 'degraded'; } else { result.services.nocodb = { status: 'down', responseTime, error: `HTTP ${response.status}` }; result.status = 'unhealthy'; } } } catch (error) { result.services.nocodb = { status: 'down', error: error instanceof Error ? error.message : 'Unknown error' }; result.status = 'unhealthy'; } // 2. Directus Check try { const directusStart = Date.now(); const { $directus } = useNuxtApp(); // Check if Directus URL is configured const directusUrl = useRuntimeConfig().public.directus?.url; if (!directusUrl) { result.services.directus = { status: 'down', error: 'Missing configuration' }; result.status = 'unhealthy'; } else { const response = await fetch(`${directusUrl}/server/health`, { signal: AbortSignal.timeout(5000) }); const responseTime = Date.now() - directusStart; if (response.ok) { result.services.directus = { status: responseTime > 3000 ? 'slow' : 'up', responseTime }; if (responseTime > 3000) result.status = 'degraded'; } else { result.services.directus = { status: 'down', responseTime, error: `HTTP ${response.status}` }; result.status = 'unhealthy'; } } } catch (error) { result.services.directus = { status: 'down', error: error instanceof Error ? error.message : 'Unknown error' }; result.status = 'unhealthy'; } // 3. MinIO Check try { const minioStart = Date.now(); const minioClient = getMinioClient(); const bucketName = useRuntimeConfig().minio.bucketName; // Try to check if bucket exists const bucketExists = await minioClient.bucketExists(bucketName); const responseTime = Date.now() - minioStart; if (bucketExists) { result.services.minio = { status: responseTime > 3000 ? 'slow' : 'up', responseTime }; if (responseTime > 3000) result.status = 'degraded'; } else { result.services.minio = { status: 'down', responseTime, error: 'Bucket not found' }; result.status = 'unhealthy'; } } catch (error) { result.services.minio = { status: 'down', error: error instanceof Error ? error.message : 'Unknown error' }; result.status = 'unhealthy'; } // 4. Documenso Check try { const documensoStart = Date.now(); const documensoUrl = process.env.NUXT_DOCUMENSO_BASE_URL; const documensoKey = process.env.NUXT_DOCUMENSO_API_KEY; if (!documensoUrl || !documensoKey) { result.services.documenso = { status: 'down', error: 'Missing configuration' }; result.status = 'unhealthy'; } else { const response = await fetch(`${documensoUrl}/api/health`, { headers: { 'Authorization': `Bearer ${documensoKey}` }, signal: AbortSignal.timeout(5000) }); const responseTime = Date.now() - documensoStart; if (response.ok || response.status === 401) { // 401 means API is up but key might be wrong result.services.documenso = { status: responseTime > 3000 ? 'slow' : 'up', responseTime }; if (responseTime > 3000) result.status = 'degraded'; } else { result.services.documenso = { status: 'down', responseTime, error: `HTTP ${response.status}` }; result.status = 'unhealthy'; } } } catch (error) { result.services.documenso = { status: 'down', error: error instanceof Error ? error.message : 'Unknown error' }; // Documenso being down shouldn't make the whole app unhealthy if (result.status === 'healthy') result.status = 'degraded'; } // Overall response time const totalTime = Date.now() - startTime; if (totalTime > 10000 && result.status === 'healthy') { result.status = 'degraded'; } // Set appropriate HTTP status code if (result.status === 'unhealthy') { setResponseStatus(event, 503); } else if (result.status === 'degraded') { setResponseStatus(event, 200); // Still return 200 for degraded to not trigger Nginx failures } return result; });