93 lines
2.7 KiB
TypeScript
93 lines
2.7 KiB
TypeScript
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<void> {
|
|
// 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<ComponentHealth> {
|
|
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,
|
|
};
|
|
}
|
|
}
|