src/app/api/cf-stream/webhook/route.ts

route·app·1.9 KB · 62 lignes· Voir l'itinéraire
Annotation non disponible

Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.

2 exports

POSTmaxDuration

Code source· typescript

import { NextRequest, NextResponse } from "next/server";
import { prisma } from "@/lib/prisma/client";
import { verifyWebhookSignature } from "@/lib/cloudflare-stream";
import { captureError } from "@/lib/sentry";

export const maxDuration = 30;

type CFStreamWebhookEvent = {
  uid?: string;
  status?: { state?: "queued" | "inprogress" | "ready" | "error" };
  thumbnail?: string;
  duration?: number;
  playback?: { hls?: string; dash?: string };
  input?: { width?: number; height?: number };
};

export async function POST(req: NextRequest) {
  const rawBody = await req.text();
  const signature = req.headers.get("webhook-signature");

  const valid = await verifyWebhookSignature(rawBody, signature);
  if (!valid) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  let event: CFStreamWebhookEvent;
  try {
    event = JSON.parse(rawBody) as CFStreamWebhookEvent;
  } catch (e) {
    captureError(e, {
      route: "/api/cf-stream/webhook",
      extra: { phase: "parse_webhook_json" },
    });
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  if (!event.uid) {
    return NextResponse.json({ ok: true, ignored: true });
  }

  if (event.status?.state === "ready") {
    await prisma.media.updateMany({
      where: { cloudflareStreamId: event.uid },
      data: {
        url: event.playback?.hls ?? "",
        cloudflareReady: true,
        thumbnailUrl: event.thumbnail ?? null,
        duration: event.duration ?? null,
        videoWidth: event.input?.width ?? null,
        videoHeight: event.input?.height ?? null,
      },
    });
  } else if (event.status?.state === "error") {
    // Cleanup : supprimer la pré-création si jamais arrivée à READY
    await prisma.media.deleteMany({
      where: { cloudflareStreamId: event.uid, cloudflareReady: false },
    });
  }

  return NextResponse.json({ ok: true });
}