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 }); } }