SPB Git

Repository Public

route.ts 1.4 KB 22 lines typescript

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
Files main
Menu

Theme