import { NextResponse } from "next/server"; import { requireAdmin } from "@/lib/admin-auth"; import { sql } from "@/db"; type Check = { name: string; status: "ok" | "warn" | "down"; detail: string }; // System health snapshot: DB connectivity + latency, migration status, recent // error volume, and which integrations are configured. Read-only and cheap — // no external round-trips, just config presence + DB queries. export async function GET(request: Request) { const auth = await requireAdmin(request); if (!auth.success) return NextResponse.json({ error: auth.error }, { status: auth.status }); const checks: Check[] = []; // 1. Database connectivity + latency let dbOk = false; try { const t0 = Date.now(); await sql`SELECT 1`; const ms = Date.now() - t0; dbOk = true; checks.push({ name: "Database", status: ms < 500 ? "ok" : "warn", detail: `Connected — ${ms}ms` }); } catch (e) { checks.push({ name: "Database", status: "down", detail: String(e).slice(0, 200) }); } // 2. Migrations applied (drizzle stores its journal in drizzle.__drizzle_migrations) if (dbOk) { try { const rows = await sql` SELECT COUNT(*)::int AS count, MAX(created_at) AS latest FROM drizzle.__drizzle_migrations `; const count = Number(rows[0]?.count) || 0; const latest = rows[0]?.latest ? new Date(Number(rows[0].latest)).toISOString().split("T")[0] : "—"; checks.push({ name: "Migrations", status: count > 0 ? "ok" : "warn", detail: `${count} applied (latest ${latest})` }); } catch { checks.push({ name: "Migrations", status: "warn", detail: "Could not read migration journal" }); } } // 3. Recent error volume (from the error tracker) let recentErrors = { last24h: 0, last1h: 0 }; if (dbOk) { try { const rows = await sql` SELECT COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '24 hours')::int AS last24h, COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '1 hour')::int AS last1h FROM error_events `; recentErrors = { last24h: Number(rows[0]?.last24h) || 0, last1h: Number(rows[0]?.last1h) || 0 }; checks.push({ name: "Errors (24h)", status: recentErrors.last1h > 5 ? "down" : recentErrors.last24h > 0 ? "warn" : "ok", detail: `${recentErrors.last24h} in 24h · ${recentErrors.last1h} in last hour`, }); } catch { checks.push({ name: "Errors (24h)", status: "warn", detail: "error_events table unavailable" }); } } // 4. Integration config presence (no live calls — just whether env is wired) const configCheck = (name: string, present: boolean, missingHint: string): Check => ({ name, status: present ? "ok" : "warn", detail: present ? "Configured" : missingHint }); checks.push(configCheck("AI Gateway", !!(process.env.LITELLM_BASE_URL && process.env.LITELLM_API_KEY), "LITELLM_BASE_URL / LITELLM_API_KEY not set")); checks.push(configCheck("R2 Storage", !!(process.env.R2_ACCOUNT_ID && process.env.R2_ACCESS_KEY_ID && process.env.R2_BUCKET_NAME), "R2_* env vars incomplete")); checks.push(configCheck("Email (Resend)", !!process.env.RESEND_API_KEY, "RESEND_API_KEY not set")); const overall: "ok" | "warn" | "down" = checks.some(c => c.status === "down") ? "down" : checks.some(c => c.status === "warn") ? "warn" : "ok"; return NextResponse.json({ overall, checks, recentErrors, checkedAt: new Date().toISOString() }); }