ghostguild-org/server/api/events/[id]/register.post.js
Jennie Robinson Faber b7279f57d1 Add Zod validation, fix mass assignment, remove test endpoints and dead code
- Add centralized Zod schemas (server/utils/schemas.js) and validateBody
  utility for all API endpoints
- Fix critical mass assignment in member creation: raw body no longer
  passed to new Member(), only validated fields (email, name, circle,
  contributionTier) are accepted
- Apply Zod validation to login, profile patch, event registration,
  updates, verify-payment, and admin event creation endpoints
- Fix logout cookie flags to match login (httpOnly: true, secure
  conditional on NODE_ENV)
- Delete unauthenticated test/debug endpoints (test-connection,
  test-subscription, test-bot)
- Remove sensitive console.log statements from Helcim and member
  endpoints
- Remove unused bcryptjs dependency
- Add 10MB file size limit on image uploads
- Use runtime config for JWT secret across all endpoints
- Add 38 validation tests (117 total, all passing)
2026-03-01 14:02:46 +00:00

143 lines
4 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 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
const member = await Member.findOne({ email: body.email.toLowerCase() });
if (eventData.membersOnly && !member) {
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 &&
!member
) {
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 (member) {
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(),
};
eventData.registrations.push(registration);
// Save the updated event
await eventData.save();
// Send confirmation email using Resend
try {
await sendEventRegistrationEmail(registration, eventData);
} catch (emailError) {
// Log error but don't fail the registration
console.error("Failed to send confirmation email:", emailError);
}
return {
success: true,
message: "Successfully registered for the event",
registrationId:
eventData.registrations[eventData.registrations.length - 1]._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",
});
}
});