LiveDisplayX / src / app / api / clerk / webhook / route.ts
route.ts
Raw
import { Webhook } from "svix";
import { headers } from "next/headers";
import { WebhookEvent } from "@clerk/nextjs/server";
import prisma from "@/lib/prisma";
import {
  UserJSON,
  DeletedObjectJSON,
  OrganizationMembershipJSON,
  OrganizationJSON,
} from "@clerk/types";

const webhookSecret = process.env.CLERK_WEBHOOK_SECRET || ``;

async function validateRequest(request: Request) {
  const payloadString = await request.text();
  const headerPayload = await headers();

  const svixHeaders = {
    "svix-id": headerPayload.get("svix-id")!,
    "svix-timestamp": headerPayload.get("svix-timestamp")!,
    "svix-signature": headerPayload.get("svix-signature")!,
  };

  const wh = new Webhook(webhookSecret);
  return wh.verify(payloadString, svixHeaders) as WebhookEvent;
}

async function handleUserCreated(userData: UserJSON) {
  await prisma.user.create({
    data: {
      id: userData.id,
      clerkUserId: userData.id,
      email: userData.email_addresses?.[0]?.email_address,
      firstName: userData.first_name,
      lastName: userData.last_name,
    },
  });
}

async function handleUserUpdated(userData: UserJSON) {
  await prisma.user.update({
    where: { clerkUserId: userData.id },
    data: {
      email: userData.email_addresses?.[0]?.email_address,
      firstName: userData.first_name,
      lastName: userData.last_name,
    },
  });
}

async function handleUserDeleted(userData: DeletedObjectJSON) {
  await prisma.user.delete({
    where: { clerkUserId: userData.id },
  });
}

async function handleOrgMembershipCreated(
  membershipData: OrganizationMembershipJSON
) {
  await prisma.organization.upsert({
    where: { id: membershipData.organization.id },
    create: {
      id: membershipData.organization.id,
      name: membershipData.organization.name,
    },
    update: {
      name: membershipData.organization.name,
    },
  });

  await prisma.user.update({
    where: { clerkUserId: membershipData.public_user_data.user_id },
    data: {
      organizations: {
        connect: { id: membershipData.organization.id },
      },
    },
  });
}

async function handleOrgMembershipDeleted(
  membershipData: OrganizationMembershipJSON
) {
  await prisma.user.update({
    where: { clerkUserId: membershipData.public_user_data.user_id },
    data: {
      organizations: {
        disconnect: { id: membershipData.organization.id },
      },
    },
  });
}

async function handleOrganizationCreated(orgData: OrganizationJSON) {
  await prisma.organization.create({
    data: {
      id: orgData.id,
      name: orgData.name,
    },
  });
}

async function handleOrganizationUpdated(orgData: OrganizationJSON) {
  await prisma.organization.update({
    where: { id: orgData.id },
    data: {
      name: orgData.name,
    },
  });
}

export async function POST(request: Request) {
  const payload = await validateRequest(request);
  const eventType = payload.type;

  switch (eventType) {
    case "user.created":
      await handleUserCreated(payload.data as unknown as UserJSON);
      break;
    case "user.updated":
      await handleUserUpdated(payload.data as unknown as UserJSON);
      break;
    case "user.deleted":
      await handleUserDeleted(payload.data as DeletedObjectJSON);
      break;
    case "organizationMembership.created":
      await handleOrgMembershipCreated(
        payload.data as OrganizationMembershipJSON
      );
      break;
    case "organizationMembership.deleted":
      await handleOrgMembershipDeleted(
        payload.data as OrganizationMembershipJSON
      );
      break;
    case "organization.created":
      await handleOrganizationCreated(
        payload.data as unknown as OrganizationJSON
      );
      break;
    case "organization.updated":
      await handleOrganizationUpdated(
        payload.data as unknown as OrganizationJSON
      );
      break;

    default:
      console.log(`Unhandled event type: ${eventType}`);
  }

  return new Response("ok", { status: 200 });
}