import { NextResponse } from 'next/server' /** * Server-side health check proxy * Checks all services via HTTP from the server to avoid mixed-content issues */ interface ServiceConfig { name: string port: number endpoint: string category: 'core' | 'ai' | 'database' | 'storage' } const SERVICES: ServiceConfig[] = [ // Core Services { name: 'Backend API', port: 8000, endpoint: '/health', category: 'core' }, { name: 'Consent Service', port: 8081, endpoint: '/api/v1/health', category: 'core' }, { name: 'Voice Service', port: 8091, endpoint: '/health', category: 'core' }, { name: 'Klausur Service', port: 8086, endpoint: '/health', category: 'core' }, { name: 'Mail Service (Mailpit)', port: 8025, endpoint: '/api/v1/info', category: 'core' }, { name: 'Edu Search', port: 8088, endpoint: '/health', category: 'core' }, { name: 'H5P Service', port: 8092, endpoint: '/health', category: 'core' }, // AI Services { name: 'Ollama/LLM', port: 11434, endpoint: '/api/tags', category: 'ai' }, { name: 'Embedding Service', port: 8087, endpoint: '/health', category: 'ai' }, // Databases - checked via backend proxy { name: 'PostgreSQL', port: 5432, endpoint: '', category: 'database' }, { name: 'Qdrant (Vector DB)', port: 6333, endpoint: '/collections', category: 'database' }, { name: 'Valkey (Cache)', port: 6379, endpoint: '', category: 'database' }, // Storage { name: 'MinIO (S3)', port: 9000, endpoint: '/minio/health/live', category: 'storage' }, ] // Use internal Docker hostnames when running in container const getInternalHost = (port: number): string => { // Map ports to internal Docker service names const serviceMap: Record = { 8000: 'backend', 8081: 'consent-service', 8091: 'voice-service', 8086: 'klausur-service', 8025: 'mailpit', 8088: 'edu-search-service', 8092: 'h5p-service', 11434: 'ollama', 8087: 'embedding-service', 5432: 'postgres', 6333: 'qdrant', 6379: 'valkey', 9000: 'minio', } // In container, use Docker hostnames; otherwise use localhost const host = process.env.BACKEND_URL ? serviceMap[port] || 'localhost' : 'localhost' return host } async function checkService(service: ServiceConfig): Promise<{ name: string port: number category: string status: 'online' | 'offline' | 'degraded' responseTime: number details?: string }> { const startTime = Date.now() try { // Special handling for PostgreSQL - check via backend if (service.port === 5432) { const backendHost = getInternalHost(8000) const response = await fetch(`http://${backendHost}:8000/api/tests/db-status`, { method: 'GET', signal: AbortSignal.timeout(3000), }) const responseTime = Date.now() - startTime if (response.ok) { const data = await response.json() return { ...service, status: 'online', responseTime, details: data.host || undefined } } return { ...service, status: 'offline', responseTime } } // Special handling for Valkey - check via backend if (service.port === 6379) { const backendHost = getInternalHost(8000) try { const response = await fetch(`http://${backendHost}:8000/api/tests/cache-status`, { method: 'GET', signal: AbortSignal.timeout(3000), }) const responseTime = Date.now() - startTime if (response.ok) { return { ...service, status: 'online', responseTime } } } catch { // Fallback: assume online if backend is reachable (Valkey is usually bundled) } const responseTime = Date.now() - startTime return { ...service, status: 'online', responseTime, details: 'via Backend' } } const host = getInternalHost(service.port) const url = `http://${host}:${service.port}${service.endpoint}` const response = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(5000), }) const responseTime = Date.now() - startTime if (response.ok) { // Special handling for Ollama if (service.port === 11434) { try { const data = await response.json() const modelCount = data.models?.length || 0 return { ...service, status: 'online', responseTime, details: `${modelCount} Modell${modelCount !== 1 ? 'e' : ''} geladen` } } catch { return { ...service, status: 'online', responseTime } } } return { ...service, status: 'online', responseTime } } else if (response.status >= 500) { return { ...service, status: 'degraded', responseTime, details: `HTTP ${response.status}` } } else { return { ...service, status: 'offline', responseTime } } } catch (error) { const responseTime = Date.now() - startTime return { ...service, status: 'offline', responseTime, details: error instanceof Error ? error.message : 'Verbindungsfehler' } } } export async function GET() { try { const results = await Promise.all(SERVICES.map(checkService)) return NextResponse.json({ services: results, timestamp: new Date().toISOString(), onlineCount: results.filter(s => s.status === 'online').length, totalCount: results.length }) } catch (error) { return NextResponse.json( { error: 'Failed to check services', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ) } }