import Member from "../../../models/member.js"; import Series from "../../../models/series.js"; import Event from "../../../models/event.js"; import { sendEventRegistrationEmail } from "../../../utils/resend.js"; import { validateBody } from "../../../utils/validateBody.js"; import { eventRegistrationSchema } from "../../../utils/schemas.js"; import { loadPublicEvent } from "../../../utils/loadEvent.js"; import { hasMemberAccess, validateTicketPurchase, checkUserSeriesPass, } from "../../../utils/tickets.js"; export default defineEventHandler(async (event) => { try { const identifier = getRouterParam(event, "id"); const body = await validateBody(event, eventRegistrationSchema); const eventData = await loadPublicEvent(event, identifier); // Series-pass gate: when an event is linked to a series and that series // requires a pass, reject drop-in registration unless the series // explicitly allows individual event tickets. if ( eventData.tickets?.requiresSeriesTicket && eventData.tickets?.seriesTicketReference ) { const series = await Series.findById( eventData.tickets.seriesTicketReference, ); if (!series) { throw createError({ statusCode: 500, statusMessage: "Series referenced by this event could not be found", }); } if (!series.tickets?.allowIndividualEventTickets) { const { hasPass } = checkUserSeriesPass(series, body.email); if (!hasPass) { throw createError({ statusCode: 403, statusMessage: "This event requires a series pass. See the series page to purchase.", data: { seriesSlug: series.slug }, }); } } } // Look up member for pricing/access purposes. hasMemberAccess treats // guest/suspended/cancelled as non-members. const member = await Member.findOne({ email: body.email.toLowerCase() }); // Single gate: validateTicketPurchase covers deadline, cancelled, already // started, already-registered, members-only, and sold-out. const validation = validateTicketPurchase(eventData, { email: body.email, name: body.name, member: hasMemberAccess(member) ? member : null, }); if (!validation.valid) { const statusCode = /members only/i.test(validation.reason) ? 403 : 400; throw createError({ statusCode, statusMessage: validation.reason }); } const memberHasAccess = hasMemberAccess(member); let isMember = false; let membershipLevel = "non-member"; if (memberHasAccess) { isMember = true; membershipLevel = `${member.circle}-${member.contributionAmount}`; } // Add registration const registration = { memberId: member ? member._id : null, name: body.name, email: body.email.toLowerCase(), membershipLevel, isMember, paymentStatus: "not_required", // Free events or member registrations amountPaid: 0, dietary: body.dietary || false, registeredAt: new Date(), }; // Use $push to avoid re-validating the whole document (e.g. legacy location formats) const result = await Event.findByIdAndUpdate( eventData._id, { $push: { registrations: registration } }, { new: true, runValidators: false }, ); const newRegistration = result.registrations[result.registrations.length - 1]; // Log activity if (member) { logActivity(member._id, 'event_registered', { eventId: eventData._id, eventTitle: eventData.title, eventSlug: eventData.slug }) } // Send confirmation email — respect member notification preferences const shouldSendEventEmail = !member || member.notifications?.events !== false; if (shouldSendEventEmail) { try { await sendEventRegistrationEmail(registration, eventData); if (member) { logActivity(member._id, 'email_sent', { emailType: 'event_registration', subject: `You're registered for ${eventData.title}` }) } } catch (emailError) { console.error("Failed to send confirmation email:", emailError); } } return { success: true, message: "Successfully registered for the event", registrationId: newRegistration._id, }; } catch (error) { console.error("Error registering for event:", error); if (error.statusCode) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to register for event", }); } });