import type { HealthResponse } from '@dorfteich/shared'; /** Result of a single dependency probe. */ export interface DatabaseProbe { ok: boolean; detail?: string; } /** * Liveness + dependency report for the collab service. Unlike the api, which * splits pure liveness (`/healthz`) from readiness (`/readyz`), the collab * skeleton only needs the one endpoint the compose healthcheck and pipeline * smoke test hit, so `/healthz` also carries the database probe (issue #33). */ export interface CollabHealthReport { status: 'ok' | 'unhealthy'; service: HealthResponse['service']; version: string; time: string; checks: { name: 'database'; status: 'ok' | 'failed'; detail?: string }[]; } /** Build the health payload and the HTTP status a monitor should see. */ export function buildHealthReport( version: string, database: DatabaseProbe, ): { httpStatus: 200 | 503; body: CollabHealthReport } { const body: CollabHealthReport = { status: database.ok ? 'ok' : 'unhealthy', service: 'collab', version, time: new Date().toISOString(), checks: [ { name: 'database', status: database.ok ? 'ok' : 'failed', ...(database.detail ? { detail: database.detail } : {}), }, ], }; return { httpStatus: database.ok ? 200 : 503, body }; } /** * Whether a plain HTTP request is a health probe. Matches both the container- * internal path (`/healthz`, used by the Docker healthcheck) and the path seen * through the host reverse proxy, which routes `/collab*` to this service * without stripping the prefix (deployment.md), i.e. `/collab/healthz`. */ export function isHealthRequest(method: string | undefined, url: string | undefined): boolean { if (method !== undefined && method !== 'GET' && method !== 'HEAD') { return false; } const pathname = pathnameOf(url); return pathname === '/healthz' || pathname === '/collab/healthz'; } function pathnameOf(url: string | undefined): string { if (!url) { return ''; } // The base only matters to parse a path-only request-target; it is discarded. return new URL(url, 'http://collab.internal').pathname.replace(/\/+$/, '') || '/'; }