import { NextResponse } from 'next/server'; import { logVisit } from '@/lib/db'; import { getClientAddress, getUserAgent, isSameOriginRequest, normalizeVisitPath, } from '@/lib/request'; export const runtime = 'nodejs'; const MAX_BODY_BYTES = 4096; function getAdminRelayUrl() { const adminUrl = process.env.ADMIN_DASH_URL; if (!adminUrl) return null; try { const url = new URL(adminUrl); if (url.protocol !== 'https:' && url.protocol !== 'http:') return null; return url.toString(); } catch { return null; } } function relayVisit(path: string, visitorId: string | null) { const adminUrl = getAdminRelayUrl(); if (!adminUrl) return; const headers: Record = { 'Content-Type': 'application/json', }; if (process.env.ADMIN_DASH_KEY) { headers.Authorization = `Bearer ${process.env.ADMIN_DASH_KEY}`; } fetch(adminUrl, { method: 'POST', body: JSON.stringify({ path, visitorId, timestamp: Date.now() }), headers, signal: AbortSignal.timeout(1500), }).catch((error) => { if (process.env.NODE_ENV !== 'production') { console.error('Analytics relay failed', error); } }); } export async function POST(req: Request) { try { if (!isSameOriginRequest(req)) { return NextResponse.json({ success: false, error: 'Forbidden' }, { status: 403 }); } const contentType = req.headers.get('content-type') || ''; if (!contentType.toLowerCase().includes('application/json')) { return NextResponse.json({ success: false, error: 'Unsupported media type' }, { status: 415 }); } const contentLength = Number(req.headers.get('content-length') || 0); if (contentLength > MAX_BODY_BYTES) { return NextResponse.json({ success: false, error: 'Payload too large' }, { status: 413 }); } const rawBody = await req.text(); if (Buffer.byteLength(rawBody, 'utf8') > MAX_BODY_BYTES) { return NextResponse.json({ success: false, error: 'Payload too large' }, { status: 413 }); } const body = JSON.parse(rawBody); if (!body || typeof body !== 'object' || Array.isArray(body)) { return NextResponse.json({ success: false, error: 'Invalid input' }, { status: 400 }); } const visitPath = normalizeVisitPath(body.path); if (!visitPath) { return NextResponse.json({ success: false, error: 'Invalid input' }, { status: 400 }); } const headers = req.headers; const clientAddress = getClientAddress(headers); const userAgent = getUserAgent(headers); let visitorId: string | null = null; try { visitorId = logVisit(clientAddress, userAgent, visitPath); } catch (e) { console.error('Failed to log visit to SQLite', e); } relayVisit(visitPath, visitorId); return NextResponse.json({ success: true }); } catch { return NextResponse.json({ success: false, error: 'Bad request' }, { status: 400 }); } }