import { sql } from "drizzle-orm"; import { NextResponse } from "next/server"; import { env } from "@/env"; import { apiJson } from "@/lib/api"; import { db } from "@/lib/db"; import { clientIp, rateLimit } from "@/lib/rate-limit"; import { redis } from "@/lib/redis"; import { rcon } from "@/lib/services/rcon"; export const dynamic = "force-dynamic"; /** * Ops health probe: database reachability, Redis (when configured), emulator * RCON, SMTP (when configured), and runtime info. Returns HTTP 200 always * (read the `status`/`database` fields), so it's safe for uptime monitors that * only care about reachability. Rate-limited per client IP. */ export async function GET() { const ip = await clientIp(); const limit = await rateLimit(`health:${ip}`, 60, 60_000); if (!limit.ok) { return NextResponse.json( { status: "rate_limited", retryAfter: limit.retryAfter }, { status: 429, headers: { "Retry-After": String(limit.retryAfter) }, }, ); } const database = await db .execute(sql`SELECT 1`) .then(() => true) .catch(() => false); let redisOk: boolean | null = null; if (env.REDIS_URL) { if (!redis) { redisOk = false; } else { try { const pong = await redis.ping(); redisOk = pong === "PONG"; } catch { redisOk = false; } } } const emulator = await rcon.send("ping", null).catch(() => false); const resendAvailable = !!env.RESEND_API_KEY; const degraded = !database || redisOk === false; return apiJson({ status: degraded ? "degraded" : "ok", database, redis: redisOk, emulator, resend: resendAvailable, node: process.version, uptime: Math.round(process.uptime()), time: new Date().toISOString(), }); }