import { NextResponse } from "next/server"; import { Registry, Gauge } from "prom-client"; import { query } from "@/lib/db"; export const runtime = "nodejs"; /** * Prometheus-compatible metrics endpoint. * * Usage in prometheus.yml: * - job_name: 'admin-metrics' * scrape_interval: 60s * static_configs: * - targets: ['admin:3000'] * metrics_path: '/api/metrics/prometheus' */ export async function GET() { const register = new Registry(); const gauge = new Gauge({ name: "platform_entity_count", help: "Platform entity counts by time window", labelNames: ["entity", "window"] as const, registers: [register], }); const entities = [ { name: "users", table: '"user"' }, { name: "workspaces", table: "workspace" }, { name: "projects", table: "project" }, { name: "repos", table: "repo" }, { name: "rooms", table: "room" }, { name: "skills", table: "project_skill" }, ]; const results = await Promise.all( entities.map(async ({ name, table }) => { const res = await query<{ total: string; new_27h: string; new_7d: string; new_30d: string; }>( `SELECT COUNT(*) AS total, COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '27 hours') AS new_27h, COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '7 days') AS new_7d, COUNT(*) FILTER (WHERE created_at >= NOW() - INTERVAL '30 days') AS new_30d FROM ${table}` ); const row = res.rows[0]; return { name, total: parseInt(row.total, 10), new_27h: parseInt(row.new_27h, 10), new_7d: parseInt(row.new_7d, 10), new_30d: parseInt(row.new_30d, 10), }; }) ); for (const r of results) { gauge.set({ entity: r.name, window: "total" }, r.total); gauge.set({ entity: r.name, window: "27h" }, r.new_27h); gauge.set({ entity: r.name, window: "7d" }, r.new_7d); gauge.set({ entity: r.name, window: "30d" }, r.new_30d); } const metrics = await register.metrics(); return new NextResponse(metrics, { headers: { "Content-Type": register.contentType }, }); }