ghostguild-org/server/api/events/[id]/register.post.js
Jennie Robinson Faber 15329e3e84 refactor(events): gate member benefits on hasMemberAccess
Extracts hasMemberAccess(member) in tickets.js and uses it across event
registration, ticket purchase, and series purchase flows so guest, suspended,
and cancelled records no longer count as members while pending_payment still
does.
2026-04-18 17:06:17 +01:00

168 lines
5 KiB
JavaScript

import Event from "../../../models/event.js";
import Member from "../../../models/member.js";
import { connectDB } from "../../../utils/mongoose.js";
import { sendEventRegistrationEmail } from "../../../utils/resend.js";
import { validateBody } from "../../../utils/validateBody.js";
import { eventRegistrationSchema } from "../../../utils/schemas.js";
import { hasMemberAccess } from "../../../utils/tickets.js";
import mongoose from "mongoose";
export default defineEventHandler(async (event) => {
try {
// Ensure database connection
await connectDB();
const identifier = getRouterParam(event, "id");
const body = await validateBody(event, eventRegistrationSchema);
if (!identifier) {
throw createError({
statusCode: 400,
statusMessage: "Event identifier is required",
});
}
// Fetch the event - try by slug first, then by ID
let eventData;
// Check if identifier is a valid MongoDB ObjectId
if (mongoose.Types.ObjectId.isValid(identifier)) {
eventData = await Event.findById(identifier);
}
// If not found by ID or not a valid ObjectId, try by slug
if (!eventData) {
eventData = await Event.findOne({ slug: identifier });
}
if (!eventData) {
throw createError({
statusCode: 404,
statusMessage: "Event not found",
});
}
// Check if event is full
if (
eventData.maxAttendees &&
eventData.registrations.length >= eventData.maxAttendees
) {
throw createError({
statusCode: 400,
statusMessage: "Event is full",
});
}
// Check if already registered
const alreadyRegistered = eventData.registrations.some(
(reg) => reg.email.toLowerCase() === body.email.toLowerCase(),
);
if (alreadyRegistered) {
throw createError({
statusCode: 400,
statusMessage: "You are already registered for this event",
});
}
// Check member status and handle different registration scenarios.
// Member access is decoupled from payment status: active and pending_payment
// both confer access; guest, suspended, and cancelled do not.
const member = await Member.findOne({ email: body.email.toLowerCase() });
const memberHasAccess = hasMemberAccess(member);
if (eventData.membersOnly && !memberHasAccess) {
throw createError({
statusCode: 403,
statusMessage:
"This event is for members only. Please become a member to register.",
});
}
// If event requires payment and user is not a member, redirect to payment flow
if (
eventData.pricing?.paymentRequired &&
!eventData.pricing?.isFree &&
!memberHasAccess
) {
throw createError({
statusCode: 402, // Payment Required
statusMessage:
"This event requires payment. Please use the payment registration endpoint.",
});
}
// Set member status and membership level
let isMember = false;
let membershipLevel = "non-member";
if (memberHasAccess) {
isMember = true;
membershipLevel = `${member.circle}-${member.contributionTier}`;
}
// 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",
});
}
});