Files
2026-02-06 21:44:04 -06:00

125 lines
4.7 KiB
TypeScript

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 = `
<h2>Webinar Registration Confirmed</h2>
<p>Hi ${user.firstName},</p>
<p>Thank you for registering for <strong>${webinar.title}</strong>.</p>
<p><strong>Date & Time:</strong> ${new Date(webinar.startAt).toLocaleString()}</p>
<p><strong>Duration:</strong> ${webinar.duration} minutes</p>
<p><strong>Join Link:</strong> <a href="${meetingLink}">${meetingLink}</a></p>
<p>A calendar invitation is attached to this email.</p>
<p>See you there!</p>
`;
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 });
}