import Event from "../../../models/event"; import Member from "../../../models/member"; import { sendEventCancellationEmail, sendWaitlistNotificationEmail, } from "../../../utils/resend.js"; import { connectDB } from "../../../utils/mongoose.js"; export default defineEventHandler(async (event) => { const id = getRouterParam(event, "id"); const body = await validateBody(event, cancelRegistrationSchema); const { email } = body; await connectDB(); try { // Check if id is a valid ObjectId or treat as slug const isObjectId = /^[0-9a-fA-F]{24}$/.test(id); const query = isObjectId ? { $or: [{ _id: id }, { slug: id }] } : { slug: id }; const eventDoc = await Event.findOne(query); if (!eventDoc) { throw createError({ statusCode: 404, statusMessage: "Event not found", }); } // Find the registration index const registrationIndex = eventDoc.registrations.findIndex( (registration) => registration.email.toLowerCase() === email.toLowerCase(), ); if (registrationIndex === -1) { throw createError({ statusCode: 404, statusMessage: "Registration not found", }); } const existingRegistration = eventDoc.registrations[registrationIndex]; const ticketType = existingRegistration.ticketType; const amountPaid = existingRegistration.amountPaid || 0; // member tickets can be free (default) or paid via circle overrides — gate on amountPaid const isPaidRegistration = ticketType === "public" || ticketType === "series_pass" || (ticketType === "member" && amountPaid > 0); if (isPaidRegistration) { throw createError({ statusCode: 403, statusMessage: "Paid registrations can't be self-cancelled. Email us for a refund — see /policies/refunds.", }); } // Store registration data before removing (convert to plain object) const registration = { name: eventDoc.registrations[registrationIndex].name, email: eventDoc.registrations[registrationIndex].email, membershipLevel: eventDoc.registrations[registrationIndex].membershipLevel, }; // Use $pull to avoid re-validating the whole document (e.g. legacy location formats) await Event.findByIdAndUpdate( eventDoc._id, { $pull: { registrations: { email: registration.email } }, $inc: { registeredCount: -1 }, }, { runValidators: false }, ); // Log activity + send cancellation confirmation email const cancellingMember = await Member.findOne({ email: registration.email, }).lean(); if (cancellingMember) { logActivity(cancellingMember._id, 'event_cancelled', { eventId: eventDoc._id, eventTitle: eventDoc.title, eventSlug: eventDoc.slug }) } try { const shouldSendCancellation = !cancellingMember || cancellingMember.notifications?.events !== false; if (shouldSendCancellation) { const eventData = { title: eventDoc.title, slug: eventDoc.slug, _id: eventDoc._id, }; await sendEventCancellationEmail(registration, eventData); if (cancellingMember) { logActivity(cancellingMember._id, 'email_sent', { emailType: 'event_cancellation', subject: `Registration cancelled for ${eventDoc.title}` }) } } } catch (emailError) { console.error("Failed to send cancellation email:", emailError); } // Notify waitlisted users if waitlist is enabled and there are entries if ( eventDoc.tickets?.waitlist?.enabled && eventDoc.tickets.waitlist.entries?.length > 0 ) { try { const eventData = { title: eventDoc.title, slug: eventDoc.slug, _id: eventDoc._id, startDate: eventDoc.startDate, endDate: eventDoc.endDate, location: eventDoc.location, }; // Notify the first person on the waitlist who hasn't been notified yet const waitlistEntry = eventDoc.tickets.waitlist.entries.find( (entry) => !entry.notified, ); if (waitlistEntry) { const waitlistedMember = await Member.findOne({ email: waitlistEntry.email, }).lean(); const shouldNotifyWaitlist = !waitlistedMember || waitlistedMember.notifications?.events !== false; if (shouldNotifyWaitlist) { await sendWaitlistNotificationEmail(waitlistEntry, eventData); } // Always mark as notified so we move on regardless const entryIndex = eventDoc.tickets.waitlist.entries.indexOf(waitlistEntry); await Event.findByIdAndUpdate( eventDoc._id, { $set: { [`tickets.waitlist.entries.${entryIndex}.notified`]: true, }, }, { runValidators: false }, ); } } catch (waitlistError) { // Log error but don't fail the cancellation console.error("Failed to notify waitlist:", waitlistError); } } return { success: true, message: "Registration cancelled successfully", registeredCount: eventDoc.registeredCount, }; } catch (error) { console.error("Error cancelling registration:", error); // Re-throw known errors if (error.statusCode) { throw error; } throw createError({ statusCode: 500, statusMessage: "Failed to cancel registration", }); } });