Files
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

60 lines
2.2 KiB
TypeScript

import { NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { getCurrentUser } from '@/lib/auth/current'
export const runtime = 'nodejs'
export const dynamic = 'force-dynamic'
const METRIKEN = new Set(['LCP', 'INP', 'CLS', 'TTFB', 'FCP'])
const BEWERTUNGEN = new Set(['good', 'needs-improvement', 'poor'])
/**
* Nimmt Ladewerte aus dem Browser entgegen.
*
* **Was hier ankommt, wird geprüft und beschnitten**, bevor es in die
* Datenbank geht: nur bekannte Metriken, nur plausible Zahlen, und die Route
* als Muster statt als konkreter Pfad. Ein Endpunkt, der ungeprüft
* Zeichenketten aus dem Browser speichert, ist ein Ablagefach für Fremde.
*
* Der Nutzer wird **nicht** mitgeschrieben. Wer wann welche Akte geöffnet hat,
* steht im Zugriffsprotokoll - dort gehört es hin, dort ist es geregelt, und
* eine zweite Spur daneben wäre eine Überwachungsfunktion ohne Auftrag.
*/
export async function POST(request: Request): Promise<Response> {
// Angemeldet sein muss man trotzdem: sonst ist es ein offener Schreibzugang.
const user = await getCurrentUser()
if (!user) return new NextResponse(null, { status: 404 })
let daten: unknown
try {
daten = await request.json()
} catch {
return new NextResponse(null, { status: 400 })
}
const eintrag = daten as Record<string, unknown>
const metric = String(eintrag.metric ?? '')
const rating = String(eintrag.rating ?? '')
const value = Number(eintrag.value)
const route = String(eintrag.route ?? '').slice(0, 120)
if (!METRIKEN.has(metric) || !BEWERTUNGEN.has(rating)) {
return new NextResponse(null, { status: 400 })
}
// CLS liegt bei 0..1, die Zeiten in Millisekunden. Alles darüber ist Unfug
// oder ein Messfehler und würde nur den Durchschnitt verderben.
if (!Number.isFinite(value) || value < 0 || value > 600_000) {
return new NextResponse(null, { status: 400 })
}
try {
await db.webVital.create({ data: { metric, value, route, rating } })
} catch (error) {
console.error('[vitals] nicht gespeichert', error)
}
// Der Browser sendet das per `sendBeacon` und interessiert sich nicht für
// die Antwort.
return new NextResponse(null, { status: 204 })
}