port-nimara-client-portal/server/api/health.ts

260 lines
7.1 KiB
TypeScript

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<HealthCheckResult> => {
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;
});