MBO-Tech-IT-Webseite/modules/03-analytics/files/app/api/analytics/track/route.ts

88 lines
2.8 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { createServiceClient } from "@/lib/supabase";
import { anonymizeIp, isBot, parseDevice, parseBrowser, parseOs } from "@/lib/analytics";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
interface TrackBody {
path: string;
session_id: string;
referrer?: string;
view_id?: string; // Für duration_ms-Updates
duration_ms?: number; // Nur bei pagehide-Updates
}
/**
* POST /api/analytics/track
*
* Insert-Modus: { path, session_id, referrer? }
* → DB INSERT, view_id zurückgeben
*
* Update-Modus: { view_id, duration_ms }
* → DB UPDATE duration_ms
*
* Bot-Anfragen: Keine DB-Eintrag, aber 200 OK zurückgeben
*/
export async function POST(req: NextRequest) {
try {
const body: TrackBody = await req.json();
// Minimale Validierung
if (!body.path || !body.session_id) {
return NextResponse.json({ ok: false }, { status: 400 });
}
const ua = req.headers.get("user-agent") ?? "";
// ──── Bot-Filter: Keine DB-Einträge für Bots ────────────────────────────
if (isBot(ua)) {
return NextResponse.json({ ok: true, bot: true });
}
const db = createServiceClient();
// ──── Update-Modus: Verweildauer setzen ────────────────────────────────
if (body.view_id && body.duration_ms !== undefined) {
await db
.from("page_views")
.update({ duration_ms: body.duration_ms })
.eq("id", body.view_id)
.is("duration_ms", null); // Idempotent: nur updaten wenn noch nicht gesetzt
return NextResponse.json({ ok: true });
}
// ──── Insert-Modus: Neuen Seitenaufruf anlegen ─────────────────────────
const rawIp =
req.headers.get("x-forwarded-for")?.split(",")[0].trim() ??
req.headers.get("x-real-ip") ??
"0.0.0.0";
const { data, error } = await db
.from("page_views")
.insert({
path: body.path,
session_id: body.session_id,
referrer: body.referrer ?? null,
ip_anon: anonymizeIp(rawIp),
device_type: parseDevice(ua),
browser: parseBrowser(ua),
os: parseOs(ua),
is_bot: false,
})
.select("id")
.single();
if (error) {
console.error("[analytics/track] DB error:", error);
return NextResponse.json({ ok: false }, { status: 500 });
}
// view_id zurückgeben für späteren duration_ms-Update
return NextResponse.json({ ok: true, view_id: data.id });
} catch (err) {
console.error("[analytics/track] Error:", err);
return NextResponse.json({ ok: false }, { status: 500 });
}
}