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
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
60 lines
2.2 KiB
TypeScript
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 })
|
|
}
|