tubearr/src/server/routes/health.ts
jlightner 29e8654b01 feat: Added GET /robots.txt route returning disallow-all and meta robot…
- "src/server/routes/health.ts"
- "src/frontend/index.html"

GSD-Task: S02/T02
2026-04-04 09:35:46 +00:00

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,
};
}
}