mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Stale-served insights responses now carry a revalidating flag; the advisor and comprehensive queries poll on a bounded 25 s cadence until the background warm lands, so an open session converges instead of pinning yesterday's briefing for an hour. Interactive measurement writes (single create, edit, delete, bulk-delete, restore) hard-evict the analytics cache so a manually entered value is reflected on the next read; background batch sync keeps the mark-stale path. The visit-triggered force warm collapses to one job per user instead of one per locale, and the umami script proxy failure now annotates a named action with the failure reason instead of a bare flag.
86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { apiHandler } from "@/lib/api-handler";
|
|
import { annotate } from "@/lib/logging/context";
|
|
import { getPublicMonitoringSettings } from "@/lib/monitoring-settings";
|
|
import { safeFetch } from "@/lib/safe-fetch";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
const NOOP_SCRIPT = "/* umami disabled */";
|
|
|
|
export const GET = apiHandler(async () => {
|
|
annotate({ action: { name: "monitoring.umami-script" } });
|
|
|
|
const settings = await getPublicMonitoringSettings();
|
|
if (
|
|
!settings.umamiEnabled ||
|
|
!settings.umamiScriptUrl ||
|
|
!settings.umamiWebsiteId
|
|
) {
|
|
return new NextResponse(NOOP_SCRIPT, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
"Cache-Control": "public, max-age=60",
|
|
},
|
|
});
|
|
}
|
|
|
|
// Fail-soft: an unreachable / slow / rebinding-refused upstream must
|
|
// degrade to the noop script, not 500 the proxy route — `safeFetch`
|
|
// THROWS on its 15 s timeout and on the connect-time public-host
|
|
// refusal, and an uncaught throw here surfaced as a 500 on every
|
|
// mount whenever the operator's Umami host was down or unresolvable.
|
|
let response: Response;
|
|
try {
|
|
response = await safeFetch(
|
|
settings.umamiScriptUrl,
|
|
{
|
|
next: { revalidate: 3600 },
|
|
},
|
|
// Operator-configured host — pin the connect-time IP so a low-TTL
|
|
// DNS record cannot rebind the fetch at an internal range between
|
|
// the input-time `isPublicUrl` accept and the socket connect.
|
|
{ requirePublicHost: true },
|
|
);
|
|
} catch (err) {
|
|
// Fail-soft stays a 200, but the failure must stay observable:
|
|
// a proper action name keeps the degraded state visible on the
|
|
// wide-event dashboards instead of hiding behind a bare meta flag.
|
|
annotate({
|
|
action: { name: "monitoring.umami_script.fetch_failed" },
|
|
meta: {
|
|
reason:
|
|
err instanceof Error
|
|
? err.message.slice(0, 200)
|
|
: String(err).slice(0, 200),
|
|
},
|
|
});
|
|
return new NextResponse(NOOP_SCRIPT, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
"Cache-Control": "public, max-age=60",
|
|
},
|
|
});
|
|
}
|
|
if (!response.ok) {
|
|
return new NextResponse(NOOP_SCRIPT, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
"Cache-Control": "public, max-age=60",
|
|
},
|
|
});
|
|
}
|
|
|
|
const script = await response.text();
|
|
return new NextResponse(script, {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "application/javascript; charset=utf-8",
|
|
"Cache-Control": "public, max-age=1800",
|
|
},
|
|
});
|
|
});
|