import { Router } from "express"; import { createRequire } from "module"; import { getPoolStats } from "../services/browser.js"; import { pool } from "../services/db.js"; const require = createRequire(import.meta.url); const { version: APP_VERSION } = require("../../package.json"); export const healthRouter = Router(); const HEALTH_CHECK_TIMEOUT_MS = 3000; /** * @openapi * /health: * get: * tags: [System] * summary: Health check * description: Returns service health status including database connectivity and browser pool stats. * responses: * 200: * description: Service is healthy * content: * application/json: * schema: * type: object * properties: * status: * type: string * enum: [ok, degraded] * version: * type: string * example: '0.4.0' * database: * type: object * properties: * status: * type: string * enum: [ok, error] * version: * type: string * example: 'PostgreSQL 17.4' * pool: * type: object * properties: * size: * type: integer * active: * type: integer * available: * type: integer * queueDepth: * type: integer * pdfCount: * type: integer * restarting: * type: boolean * uptimeSeconds: * type: integer * 503: * description: Service is degraded (database issue) */ healthRouter.get("/", async (_req, res) => { const poolStats = getPoolStats(); let databaseStatus; let overallStatus = "ok"; let httpStatus = 200; // Check database connectivity with a real query and timeout try { const dbCheck = async () => { const client = await pool.connect(); try { // Use SELECT 1 as a lightweight liveness probe await client.query('SELECT 1'); const result = await client.query('SELECT version()'); const version = result.rows[0]?.version || 'Unknown'; const versionMatch = version.match(/PostgreSQL ([\d.]+)/); const shortVersion = versionMatch ? `PostgreSQL ${versionMatch[1]}` : 'PostgreSQL'; client.release(); return { status: "ok", version: shortVersion }; } catch (queryErr) { // Destroy the bad connection so it doesn't go back to the pool try { client.release(true); } catch (_) { } throw queryErr; } }; const timeout = new Promise((_resolve, reject) => setTimeout(() => reject(new Error("Database health check timed out")), HEALTH_CHECK_TIMEOUT_MS)); databaseStatus = await Promise.race([dbCheck(), timeout]); } catch (error) { databaseStatus = { status: "error", message: error instanceof Error ? error.message : "Database connection failed" }; overallStatus = "degraded"; httpStatus = 503; } const response = { status: overallStatus, version: APP_VERSION, database: databaseStatus, pool: { size: poolStats.poolSize, active: poolStats.totalPages - poolStats.availablePages, available: poolStats.availablePages, queueDepth: poolStats.queueDepth, pdfCount: poolStats.pdfCount, restarting: poolStats.restarting, uptimeSeconds: Math.round(poolStats.uptimeMs / 1000), }, }; res.status(httpStatus).json(response); });