- Fix path traversal in file serve endpoint (validate company prefix, block ..) - Add typed error classes: ValidationError, NotFoundError, ForbiddenError, ConflictError, StorageError - Global error handler catches AppError subclasses with correct status codes - 4xx logged as warn, 5xx as error with request ID - File upload validates entityType whitelist, UUID format, category pattern - Remove fragile string-matching error handling from routes - Services throw typed errors instead of plain Error - Health endpoint documented as intentionally public
34 lines
1023 B
TypeScript
34 lines
1023 B
TypeScript
import type { FastifyPluginAsync } from 'fastify'
|
|
import { sql } from 'drizzle-orm'
|
|
|
|
export const healthRoutes: FastifyPluginAsync = async (app) => {
|
|
// Intentionally public — no auth. Load balancers, Docker health checks, and monitoring need this.
|
|
app.get('/health', async (request, reply) => {
|
|
let dbStatus = 'disconnected'
|
|
let redisStatus = 'disconnected'
|
|
|
|
try {
|
|
await app.db.execute(sql`SELECT 1`)
|
|
dbStatus = 'connected'
|
|
} catch (err) {
|
|
request.log.error({ err }, 'Database health check failed')
|
|
}
|
|
|
|
try {
|
|
const pong = await app.redis.ping()
|
|
redisStatus = pong === 'PONG' ? 'connected' : 'disconnected'
|
|
} catch (err) {
|
|
request.log.error({ err }, 'Redis health check failed')
|
|
}
|
|
|
|
const healthy = dbStatus === 'connected' && redisStatus === 'connected'
|
|
|
|
reply.status(healthy ? 200 : 503).send({
|
|
status: healthy ? 'ok' : 'degraded',
|
|
db: dbStatus,
|
|
redis: redisStatus,
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
})
|
|
}
|