import { NextRequest } from "next/server"; import { z } from "zod"; import { getSession } from "../../../lib/auth/session"; import { getPrisma } from "../../../lib/db"; import { ok, fail } from "../../../lib/http"; import { getStripe } from "../../../lib/stripe"; import { loadSystemConfig } from "../../../lib/system-config"; import { sendEmail } from "../../../lib/email"; import { createWebinarCalendarEvent } from "../../../lib/calendar"; export const runtime = "nodejs"; const Body = z.object({ webinarId: z.string().uuid(), }); export async function POST(req: NextRequest) { const session = await getSession(); if (!session) return fail(new Error("Unauthorized"), { status: 401 }); const prisma = await getPrisma(); if (!prisma) return fail(new Error("Database not configured"), { status: 503 }); // Check if user is admin - prevent admin registration for webinars const user = await prisma.user.findUnique({ where: { id: session.sub } }); if (user?.role === "ADMIN") { return fail(new Error("Admins cannot register for webinars"), { status: 403 }); } const parsed = Body.safeParse(await req.json().catch(() => ({}))); if (!parsed.success) return fail(new Error("Invalid input")); const webinar = await prisma.webinar.findUnique({ where: { id: parsed.data.webinarId } }); if (!webinar || webinar.visibility !== "PUBLIC" || !webinar.isActive) return fail(new Error("Not found"), { status: 404 }); const count = await prisma.webinarRegistration.count({ where: { webinarId: webinar.id, status: { not: "CANCELLED" } }, }); if (count >= webinar.capacity) return fail(new Error("Webinar is full"), { status: 409 }); // Upsert registration if (webinar.priceCents <= 0) { const reg = await prisma.webinarRegistration.upsert({ where: { userId_webinarId: { userId: session.sub, webinarId: webinar.id } }, update: { status: "CONFIRMED" }, create: { userId: session.sub, webinarId: webinar.id, status: "CONFIRMED" }, }); // Send confirmation email with calendar invite for free webinars const cfg = await loadSystemConfig(); if (cfg.email?.enabled && user) { try { const { icsContent } = await createWebinarCalendarEvent(webinar, user.email); const meetingInfo = webinar.meetingInfo as any; const meetingLink = meetingInfo?.meetingLink || "TBD"; const htmlContent = `

Webinar Registration Confirmed

Hi ${user.firstName},

Thank you for registering for ${webinar.title}.

Date & Time: ${new Date(webinar.startAt).toLocaleString()}

Duration: ${webinar.duration} minutes

Join Link: ${meetingLink}

A calendar invitation is attached to this email.

See you there!

`; await sendEmail({ to: user.email, subject: `Registration Confirmed: ${webinar.title}`, html: htmlContent, attachments: [ { filename: "webinar-invite.ics", content: icsContent, contentType: "text/calendar", }, ], }); } catch (error) { console.error("[REGISTRATION] Failed to send email:", error); // Don't fail the registration if email fails } } return ok({ registration: reg, next: "CONFIRMED" }); } const cfg = await loadSystemConfig(); const stripe = await getStripe(); if (!stripe) return fail(new Error("Stripe not configured"), { status: 503 }); const baseUrl = cfg.app?.initialized ? (process.env.APP_BASE_URL || "http://localhost:3000") : (process.env.APP_BASE_URL || "http://localhost:3000"); const reg = await prisma.webinarRegistration.upsert({ where: { userId_webinarId: { userId: session.sub, webinarId: webinar.id } }, update: { status: "PAYMENT_PENDING" }, create: { userId: session.sub, webinarId: webinar.id, status: "PAYMENT_PENDING" }, }); const checkout = await stripe.checkout.sessions.create({ mode: "payment", success_url: `${baseUrl}/webinars/${webinar.id}?payment=success`, cancel_url: `${baseUrl}/webinars/${webinar.id}?payment=cancel`, line_items: [ { quantity: 1, price_data: { currency: "usd", unit_amount: webinar.priceCents, product_data: { name: webinar.title }, }, }, ], metadata: { registrationId: reg.id, userId: session.sub, webinarId: webinar.id }, }); await prisma.webinarRegistration.update({ where: { id: reg.id }, data: { stripeCheckoutSessionId: checkout.id }, }); return ok({ next: "STRIPE_CHECKOUT", url: checkout.url }); }