import { type FastifyInstance } from 'fastify'; import { getRawClient } from '../../db/index'; import type { HealthResponse, ComponentHealth } from '../../types/api'; const serverStartTime = Date.now(); /** * Health route plugin. * * Registers: * GET /ping — unauthenticated liveness probe (Docker health checks) * GET /api/v1/health — authenticated component health status */ export async function healthRoutes(fastify: FastifyInstance): Promise { // Unauthenticated liveness probe fastify.get('/ping', async (_request, _reply) => { return { status: 'ok' }; }); // robots.txt — disallow all crawlers (private media manager) fastify.get('/robots.txt', async (_request, reply) => { return reply .type('text/plain') .send('User-agent: *\nDisallow: /\n'); }); // Authenticated component health fastify.get('/api/v1/health', async (_request, _reply) => { const components: ComponentHealth[] = []; // Database health check const dbHealth = await checkDatabaseHealth(); components.push(dbHealth); // Server health (always healthy if we're responding) components.push({ name: 'server', status: 'healthy', message: 'Fastify server is running', }); // Health service components (scheduler, yt-dlp, disk space, recent errors) if (fastify.healthService) { const serviceComponents = await fastify.healthService.getComponentHealth(); components.push(...serviceComponents); } // Overall status: degraded if any component is degraded, unhealthy if any is unhealthy let overallStatus: HealthResponse['status'] = 'healthy'; if (components.some((c) => c.status === 'unhealthy')) { overallStatus = 'unhealthy'; } else if (components.some((c) => c.status === 'degraded')) { overallStatus = 'degraded'; } const uptimeSeconds = Math.floor((Date.now() - serverStartTime) / 1000); const response: HealthResponse = { status: overallStatus, components, uptime: uptimeSeconds, }; return response; }); } /** * Check database connectivity by running a simple query. */ async function checkDatabaseHealth(): Promise { const start = Date.now(); try { const client = getRawClient(); await client.execute('SELECT 1'); const elapsed = Date.now() - start; return { name: 'database', status: 'healthy', message: 'SQLite connection is active', responseTime: elapsed, }; } catch (err) { const elapsed = Date.now() - start; return { name: 'database', status: 'unhealthy', message: err instanceof Error ? err.message : 'Database check failed', responseTime: elapsed, }; } }