import { Webhook } from "svix"; import { headers } from "next/headers"; import { WebhookEvent } from "@clerk/nextjs/server"; import { NextResponse } from "next/server"; import Database from "better-sqlite3"; import path from "path"; export async function POST(req: Request) { // Get the webhook secret from environment variables const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET; if (!WEBHOOK_SECRET) { throw new Error( "Please add CLERK_WEBHOOK_SECRET to your environment variables", ); } // Get the headers const headerPayload = await headers(); const svix_id = headerPayload.get("svix-id"); const svix_timestamp = headerPayload.get("svix-timestamp"); const svix_signature = headerPayload.get("svix-signature"); // If there are no headers, error out if (!svix_id || !svix_timestamp || !svix_signature) { return NextResponse.json( { error: "Missing svix headers" }, { status: 400 }, ); } // Get the body const payload = await req.json(); const body = JSON.stringify(payload); // Create a new Svix instance with your webhook secret const wh = new Webhook(WEBHOOK_SECRET); let evt: WebhookEvent; // Verify the webhook signature try { evt = wh.verify(body, { "svix-id": svix_id, "svix-timestamp": svix_timestamp, "svix-signature": svix_signature, }) as WebhookEvent; } catch (err) { console.error("Error verifying webhook:", err); return NextResponse.json({ error: "Verification failed" }, { status: 400 }); } // Handle the webhook const eventType = evt.type; console.log(`Received webhook with ID ${evt.data.id} and type ${eventType}`); try { // Connect to database directly for webhook operations const dbPath = path.join(process.cwd(), "data", "fitai.db"); const db = new Database(dbPath); switch (eventType) { case "user.created": { const { id, email_addresses, first_name, last_name, public_metadata } = evt.data; // Get primary email const primaryEmail = email_addresses.find( (email) => email.id === evt.data.primary_email_address_id, ); if (!primaryEmail?.email_address) { console.error("No primary email found for user:", id); db.close(); return NextResponse.json( { error: "No primary email" }, { status: 400 }, ); } // Determine role from metadata or default to 'client' const role = (public_metadata?.role as "admin" | "trainer" | "client") || "client"; // Insert user into database with Clerk's user ID const now = new Date().toISOString(); const stmt = db.prepare(` INSERT INTO users (id, email, firstName, lastName, password, phone, role, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( id, primaryEmail.email_address, first_name || "", last_name || "", "", // Clerk handles authentication null, // phone role, now, now, ); console.log(`✅ User ${id} created in database`); db.close(); break; } case "user.updated": { const { id, email_addresses, first_name, last_name, public_metadata } = evt.data; // Get primary email const primaryEmail = email_addresses.find( (email) => email.id === evt.data.primary_email_address_id, ); if (!primaryEmail?.email_address) { console.error("No primary email found for user:", id); db.close(); return NextResponse.json( { error: "No primary email" }, { status: 400 }, ); } // Determine role from metadata const role = (public_metadata?.role as "admin" | "trainer" | "client") || "client"; // Update user in database const now = new Date().toISOString(); const stmt = db.prepare(` UPDATE users SET email = ?, firstName = ?, lastName = ?, role = ?, updatedAt = ? WHERE id = ? `); stmt.run( primaryEmail.email_address, first_name || "", last_name || "", role, now, id, ); console.log(`✅ User ${id} updated in database`); db.close(); break; } case "user.deleted": { const { id } = evt.data; if (!id) { console.error("No user ID provided for deletion"); db.close(); return NextResponse.json({ error: "No user ID" }, { status: 400 }); } // Delete user from database (cascade will handle related records) const stmt = db.prepare("DELETE FROM users WHERE id = ?"); stmt.run(id); console.log(`✅ User ${id} deleted from database`); db.close(); break; } default: console.log(`Unhandled webhook event type: ${eventType}`); db.close(); } return NextResponse.json( { message: "Webhook processed successfully" }, { status: 200 }, ); } catch (error) { console.error("Error processing webhook:", error); return NextResponse.json({ error: "Processing failed" }, { status: 500 }); } }