import { NextResponse } from "next/server";import { revalidateTag } from "next/cache";import { getCloudflareContext } from "@opennextjs/cloudflare";import { CACHE_STATS_TAG, CACHE_CONTENT_TAG } from "@/utilities/githubRepo";async function getWebhookSecret(): Promise<string | undefined> { try { const value = (getCloudflareContext().env as Record<string, unknown>).GITHUB_WEBHOOK_SECRET; if (typeof value === "string" && value) return value; } catch { } return process.env.GITHUB_WEBHOOK_SECRET || undefined;}async function isValidSignature(secret: string, body: string, header: string | null): Promise<boolean> { if (!header) return false; const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signed = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(body)); const expected = "sha256=" + [...new Uint8Array(signed)].map((b) => b.toString(16).padStart(2, "0")).join(""); return timingSafeEqual(expected, header);}function timingSafeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; let mismatch = 0; for (let i = 0; i < a.length; i++) mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i); return mismatch === 0;}export async function POST(request: Request): Promise<Response> { const secret = await getWebhookSecret(); if (!secret) return NextResponse.json({ error: "webhook not configured" }, { status: 503 }); const body = await request.text(); const signature = request.headers.get("x-hub-signature-256"); if (!(await isValidSignature(secret, body, signature))) { return NextResponse.json({ error: "invalid signature" }, { status: 401 }); } if (request.headers.get("x-github-event") === "ping") { return NextResponse.json({ ok: true, pong: true }); } let touchedContent = true; try { const payload = JSON.parse(body) as { ref?: string; commits?: { added?: string[]; modified?: string[]; removed?: string[] }[]; }; if (payload.ref && payload.ref !== "refs/heads/main") { return NextResponse.json({ ok: true, skipped: "non-main ref" }); } const touched = (payload.commits ?? []).flatMap((c) => [ ...(c.added ?? []), ...(c.modified ?? []), ...(c.removed ?? []), ]); touchedContent = touched.length === 0 || touched.some((f) => f.startsWith("content/")); } catch { } revalidateTag(CACHE_STATS_TAG); if (touchedContent) revalidateTag(CACHE_CONTENT_TAG); return NextResponse.json({ ok: true, revalidated: touchedContent ? [CACHE_STATS_TAG, CACHE_CONTENT_TAG] : [CACHE_STATS_TAG], });}