Repository Public
1import { sql } from "@/lib/db";2import { sameOrigin, visitorHash } from "@/lib/auth";3import { rateLimit } from "@/lib/ratelimit";4import { bad } from "@/lib/api";56const BOT = /bot|crawl|spider|slurp|preview|headless|lighthouse|curl|wget|python|monitor/i;7/** POST /api/events — anonymous view / complete / share counters. One event per film, kind and daily visitor hash. */8export async function POST(req: Request) {9 if (!sameOrigin(req)) return bad("bad_origin", 403);10 if (BOT.test(req.headers.get("user-agent") ?? "")) return new Response(null, { status: 204 });11 const b = (await req.json().catch(() => null)) as { filmId?: string; kind?: string; locale?: string } | null;12 if (!b || !/^[0-9a-f-]{36}$/.test(b.filmId ?? "") || !["view", "complete", "share"].includes(b.kind ?? "")) return bad("invalid_body");13 const visitor = await visitorHash();14 if (!(await rateLimit(`ev:${visitor}`, 300, 3600))) return bad("rate_limited", 429);15 const inserted = await sql`16 INSERT INTO analytics_events (film_id, kind, visitor, locale)17 SELECT id, ${b.kind!}, ${visitor}, ${b.locale === "fr" ? "fr" : "en"} FROM films WHERE id = ${b.filmId!} AND status = 'published'18 ON CONFLICT DO NOTHING RETURNING id`;19 if (inserted.length && b.kind === "view") await sql`UPDATE films SET views = views + 1 WHERE id = ${b.filmId!}`;20 return new Response(null, { status: 204 });21}22