Files
PolyWeather/frontend/app/api/vitals/route.ts
2026-04-06 13:58:11 +08:00

91 lines
2.4 KiB
TypeScript

import { NextResponse } from "next/server";
import {
getVitalsSummary,
normalizeMetricName,
recordVitalsSample,
} from "@/lib/vitals-store";
const WEB_VITALS_ENABLED =
process.env.NEXT_PUBLIC_POLYWEATHER_WEB_VITALS === "true";
type VitalsPayload = {
id?: string;
metric?: string;
navigationType?: string;
pathname?: string;
rating?: string;
value?: number;
};
export async function POST(request: Request) {
if (!WEB_VITALS_ENABLED) {
return new NextResponse(null, { status: 204 });
}
try {
const payload = (await request.json()) as VitalsPayload;
const metric = normalizeMetricName(payload.metric);
if (!metric) {
return NextResponse.json({ ok: false, error: "metric is required" }, { status: 400 });
}
const pathname = String(payload.pathname || "/");
const rating = String(payload.rating || "unknown");
const value = Number(payload.value);
const navigationType = String(payload.navigationType || "unknown");
const id = String(payload.id || "");
const ts = Date.now();
if (!Number.isFinite(value)) {
return NextResponse.json({ ok: false, error: "value must be finite" }, { status: 400 });
}
recordVitalsSample({
id,
metric,
navigationType,
pathname,
rating,
timestamp: ts,
value,
});
// Keep this lightweight: log for now, can be wired to a persistent sink later.
console.info(
`[vitals] metric=${metric} path=${pathname} value=${Number.isFinite(value) ? value : "NaN"} rating=${rating} nav=${navigationType} id=${id}`,
);
return NextResponse.json({ ok: true });
} catch (error) {
console.warn("[vitals] failed to parse payload", error);
return NextResponse.json({ ok: false }, { status: 400 });
}
}
export async function GET(request: Request) {
if (!WEB_VITALS_ENABLED) {
return NextResponse.json({
ok: true,
disabled: true,
generatedAt: Date.now(),
sampleCount: 0,
routes: {},
});
}
const { searchParams } = new URL(request.url);
const targetRoute = String(searchParams.get("route") || "").trim();
const summary = getVitalsSummary();
if (!targetRoute) {
return NextResponse.json({ ok: true, ...summary });
}
return NextResponse.json({
ok: true,
generatedAt: summary.generatedAt,
route: targetRoute,
metrics: summary.routes[targetRoute] || {},
});
}