import { getDatabase } from "@/db"; import { sql } from "drizzle-orm"; import type { APIRoute } from "astro"; import { withErrorHandler } from "@/lib/errors"; import { isDistributedLocking } from "@/lib/distributed-lock"; import { isDistributed as isDistributedRateLimit } from "@/lib/rate-limit"; import { getQueueWorkerScalingReadiness } from "@/lib/queue-worker"; export const GET: APIRoute = withErrorHandler(async () => { const checks: Record< string, { status: "ok" | "error"; message?: string; latency?: number; details?: unknown } > = {}; // Check database const dbStart = Date.now(); try { const db = getDatabase() as any; if (db.execute) { await db.execute(sql`SELECT 1`); } else { await db.run(sql`SELECT 1`); } checks.database = { status: "ok", latency: Date.now() - dbStart }; } catch (error) { checks.database = { status: "error", message: error instanceof Error ? error.message : "Unknown error", }; } // Check Redis (if configured) if (process.env.REDIS_URL) { const redisStart = Date.now(); try { const { redis } = await import("@/lib/redis"); const pong = await redis.ping(); if (pong === "PONG" || pong === "OK") { checks.redis = { status: "ok", latency: Date.now() - redisStart }; } else { checks.redis = { status: "error", message: `Unexpected ping response: ${pong}` }; } } catch (error) { checks.redis = { status: "error", message: error instanceof Error ? error.message : "Unknown error", }; } } // Check storage try { const fs = await import("fs/promises"); const storagePath = process.env.STORAGE_PATH || "./data/storage"; await fs.mkdir(storagePath, { recursive: true }).catch(() => {}); await fs.access(storagePath); checks.storage = { status: "ok" }; } catch (error) { checks.storage = { status: "error", message: error instanceof Error ? error.message : "Storage not accessible", }; } // Check horizontal scaling readiness for self-hosted deployments const queueWorker = getQueueWorkerScalingReadiness(); const redisConfigured = Boolean(process.env.REDIS_URL?.trim()); const production = process.env.NODE_ENV === "production"; const scalingIssues: string[] = []; if (production && process.env.SKIP_REDIS_CHECK !== "1") { if (!redisConfigured) { scalingIssues.push("REDIS_URL is required in production for distributed coordination"); } if (!isDistributedLocking) { scalingIssues.push("Distributed locking is not active"); } if (!isDistributedRateLimit) { scalingIssues.push("Distributed rate limiting is not active"); } if (!queueWorker.multiInstanceSafe) { scalingIssues.push("Queue worker is not multi-instance safe"); } } checks.scaling = { status: scalingIssues.length ? "error" : "ok", ...(scalingIssues.length ? { message: scalingIssues.join("; ") } : {}), details: { production, redisConfigured, distributedLocking: isDistributedLocking, distributedRateLimit: isDistributedRateLimit, queueWorker, }, }; // Overall health const isHealthy = Object.values(checks).every( (check) => check.status === "ok" ); return new Response( JSON.stringify({ status: isHealthy ? "healthy" : "unhealthy", timestamp: new Date(), version: process.env.npm_package_version || "1.0.0", uptime: process.uptime(), checks, }), { status: isHealthy ? 200 : 503, headers: { "Content-Type": "application/json", "Cache-Control": "no-store, no-cache, must-revalidate", }, } ); });