diff --git a/backend/src/controllers/registrationController.js b/backend/src/controllers/registrationController.js index ce5d7aa..adffec9 100644 --- a/backend/src/controllers/registrationController.js +++ b/backend/src/controllers/registrationController.js @@ -11,45 +11,11 @@ import { createObjectCsvWriter } from 'csv-writer'; import { emitRegistrationCount } from '../services/socket.js'; import { createNotification } from './notificationController.js'; - export const registerForEvent = async (req, res) => { try { const event = await Event.findById(req.params.id); - if (!event || event.status !== 'approved') return res.status(400).json({ message: 'Event not available' }); - const payload = JSON.stringify({ userId: req.user.id, eventId: event._id, at: Date.now() }); - const qrCodeDataUrl = await generateQRCodeDataUrl(payload); - - // Current implementation includes : - // Checks for an existing cancelled registration - // Reactivating the existing registration instead of inserting a new record - // Capacity validation on event registration - // Keeps the audit trail intact while avoiding unique index conflicts - - // Check active registration - const activeRegistrations = await Registration.countDocuments({ - event: req.params.id, - status: { $ne: "cancelled" }, - }); - - // Capacity validation - if (activeRegistrations >= event.capacity && event.capacity > 0) { - return res.status(400).json({ - message: "Event is fully booked" - }) - } - - // To reinitiate the existing registered event - const existingRegistration = await Registration.findOne({ user: req.user.id, event: req.params.id }); - - if (existingRegistration) { - if (existingRegistration.status === "cancelled") { - existingRegistration.status = 'registered'; - } - if (!event || event.status !== 'approved') { - return res.status(400).json({ - message: 'Event not available', - }); + return res.status(400).json({ message: 'Event not available' }); } // Check existing registration @@ -61,48 +27,21 @@ export const registerForEvent = async (req, res) => { // Already active if ( existingRegistration && - ['registered', 'waitlisted', 'attended'].includes( - existingRegistration.status - ) + ['registered', 'waitlisted', 'attended'].includes(existingRegistration.status) ) { return res.status(400).json({ message: 'Already registered or waitlisted', }); } - // Atomically increment count only if under capacity - const updatedEvent = await Event.findOneAndUpdate( - { - _id: event._id, - status: 'approved', - $expr: { - $lt: ['$registeredCount', '$capacity'], - }, - }, - { - $inc: { - registeredCount: 1, - }, - }, - { - new: true, - } - ); - - return res.status(201).json({ - registration: existingRegistration, - }) - } + const activeRegistrations = await Registration.countDocuments({ + event: req.params.id, + status: { $ne: "cancelled" }, + }); - else { - const reg = await Registration.create({ user: req.user.id, event: event._id, qrCodeDataUrl }); - try { - await sendEmail({ to: req.user.email, subject: `Registered: ${event.title}`, html: `
You are registered for ${event.title}.
` }); - } catch (_) { } - // Event is full — reject immediately, no registration created - if (!updatedEvent) { + if (activeRegistrations >= event.capacity && event.capacity > 0) { return res.status(400).json({ - message: 'Event is full', + message: "Event is fully booked" }); } @@ -112,58 +51,32 @@ export const registerForEvent = async (req, res) => { at: Date.now(), }); - const qrCodeDataUrl = - await generateQRCodeDataUrl(payload); + const qrCodeDataUrl = await generateQRCodeDataUrl(payload); let registration; - // Reuse cancelled registration - if ( - existingRegistration && - existingRegistration.status === 'cancelled' - ) { + if (existingRegistration && existingRegistration.status === "cancelled") { existingRegistration.status = 'registered'; - existingRegistration.qrCodeDataUrl = - qrCodeDataUrl; - - registration = - await existingRegistration.save(); + existingRegistration.qrCodeDataUrl = qrCodeDataUrl; + registration = await existingRegistration.save(); } else { - try { - registration = await Registration.create({ - user: req.user.id, - event: event._id, - qrCodeDataUrl, - status: 'registered', - }); - } catch (dupErr) { - if (dupErr.code === 11000) { - await Event.findByIdAndUpdate( - event._id, - { - $inc: { - registeredCount: -1, - }, - } - ); - - return res.status(400).json({ - message: - 'Already registered or waitlisted', - }); - } - - throw dupErr; - } + registration = await Registration.create({ + user: req.user.id, + event: event._id, + qrCodeDataUrl, + status: 'registered', + }); } - - // Send email - emitRegistrationCount( - updatedEvent._id, - updatedEvent.registeredCount, + // Increment count + const updatedEvent = await Event.findByIdAndUpdate( + event._id, + { $inc: { registeredCount: 1 } }, + { new: true } ); + emitRegistrationCount(updatedEvent._id, updatedEvent.registeredCount); + try { await sendEmail({ to: req.user.email, @@ -172,7 +85,6 @@ export const registerForEvent = async (req, res) => { }); } catch (_) {} - // Send notification try { await createNotification( req.user.id, @@ -190,82 +102,24 @@ export const registerForEvent = async (req, res) => { }); } catch (err) { console.error('ERROR:', err); - res.status(500).json({ message: err.message, }); } }; -// Fetch registrations with waitlist position -export const myRegistrations = async ( - req, - res -) => { +export const myRegistrations = async (req, res) => { try { const regs = await Registration.find({ user: req.user.id }).populate('event'); - - - const payload = JSON.stringify({ userId: req.user.id, eventId: event._id, at: Date.now() }); - const qrCodeDataUrl = await generateQRCodeDataUrl(payload); - - // Current implementation includes : - // Checks for an existing cancelled registration - // Reactivating the existing registration instead of inserting a new record - // Capacity validation on event registration - // Keeps the audit trail intact while avoiding unique index conflicts - - // Check active registration - const activeRegistrations = await Registration.countDocuments({ - event: req.params.id, - status: { $ne: "cancelled" }, + res.json({ + registrations: regs }); - - // Capacity validation - if (activeRegistrations>=event.capacity && event.capacity>0){ - return res.status(400).json({ - message:"Event is fully booked" - }) - } - - // To reinitiate the existing registered event - const existingRegistration = await Registration.findOne({user:req.user.id,event:req.params.id}); - - if (existingRegistration){ - if (existingRegistration.status==="cancelled"){ - existingRegistration.status = 'registered'; - } - - await existingRegistration.save(); - try { - await sendEmail({ to: req.user.email, subject: `Registered: ${event.title}`, html: `You are registered for ${event.title}.
` }); - } catch (_) { } - - return res.status(201).json({ - registration:existingRegistration, - }) - } - - else{ - const reg = await Registration.create({ user: req.user.id, event: event._id, qrCodeDataUrl }); - try { - await sendEmail({ to: req.user.email, subject: `Registered: ${event.title}`, html: `You are registered for ${event.title}.
` }); - } catch (_) { } - - res.status(201).json({ registration: reg }); - } - - - } catch (err) { console.error("ERROR:", err); res.status(500).json({ message: err.message }); } }; -// fetching registrations with waiting position - - export const participantsForEvent = async (req, res) => { try { const regs = await Registration.find({ @@ -275,74 +129,31 @@ export const participantsForEvent = async (req, res) => { res.json({ participants: regs, }); - } catch (err) { console.error('ERROR:', err); - res.status(500).json({ message: err.message, }); } }; -// Get participants for organizer/admin -export const participantsForEvent = - async (req, res) => { - try { - const regs = await Registration.find({ - event: req.params.id, - }).populate('user', 'name email'); +export const checkInParticipant = async (req, res) => { + try { + if (!req.user) { + return res.status(401).json({ message: 'Unauthorized: user not authenticated' }); + } - res.json({ - participants: regs, - }); - } catch (err) { - console.error('ERROR:', err); + if (!req.body || !req.body.userId) { + return res.status(400).json({ message: 'Bad Request: userId is required' }); + } - res.status(500).json({ - message: err.message, - }); + const validStatuses = ['attended', 'cancelled', 'no-show']; + const status = (req.body.status || 'attended').toString().trim().toLowerCase(); + + if (!validStatuses.includes(status)) { + return res.status(400).json({ message: 'Invalid status' }); } - }; -// Secure check-in handler -export const checkInParticipant = - async (req, res) => { - try { - if (!req.user) { - return res.status(401).json({ - message: - 'Unauthorized: user not authenticated', - }); - } - - if (!req.body || !req.body.userId) { - return res.status(400).json({ - message: - 'Bad Request: userId is required', - }); - } - - const validStatuses = [ - 'attended', - 'cancelled', - 'no-show', - ]; - - const status = ( - req.body.status || 'attended' - ) - .toString() - .trim() - .toLowerCase(); - - if (!validStatuses.includes(status)) { - return res.status(400).json({ - message: 'Invalid status', - }); - } - - // Perform atomic update const registration = await Registration.findOneAndUpdate( { event: req.params.id, user: req.body.userId }, { status }, @@ -358,8 +169,6 @@ export const checkInParticipant = } }; - - export const exportParticipantsCsv = async (req, res) => { try { const regs = await Registration.find({ event: req.params.id }).populate('user', 'name email'); @@ -387,10 +196,13 @@ export const exportParticipantsCsv = async (req, res) => { } }; - isWaitlisted: - registration?.status === - 'waitlisted', - +export const checkRegistrationStatus = async (req, res) => { + try { + const registration = await Registration.findOne({ + user: req.user.id, + event: req.params.id + }); + res.json({ isRegistered: registration?.status === 'registered', isWaitlisted: registration?.status === 'waitlisted', @@ -403,8 +215,6 @@ export const exportParticipantsCsv = async (req, res) => { } }; - -// promoting from waitlist to registered export const promoteFromWaitlist = async (eventId) => { const nextRegistration = await Registration.findOne({ event: eventId, @@ -413,53 +223,45 @@ export const promoteFromWaitlist = async (eventId) => { .sort({ createdAt: 1 }) .populate('user') .populate('event'); - if (!nextRegistration) { - return; -} - const payload = JSON.stringify({ - userId: - nextRegistration.user._id, - eventId: - nextRegistration.event._id, - at: Date.now(), - }); - - const qrCodeDataUrl = - await generateQRCodeDataUrl( - payload - ); + if (!nextRegistration) { + return; + } - nextRegistration.status = - 'registered'; + const payload = JSON.stringify({ + userId: nextRegistration.user._id, + eventId: nextRegistration.event._id, + at: Date.now(), + }); - nextRegistration.qrCodeDataUrl = - qrCodeDataUrl; + const qrCodeDataUrl = await generateQRCodeDataUrl(payload); - await nextRegistration.save(); + nextRegistration.status = 'registered'; + nextRegistration.qrCodeDataUrl = qrCodeDataUrl; + await nextRegistration.save(); - try { - await sendEmail({ - to: nextRegistration.user.email, - subject: `Spot Confirmed: ${nextRegistration.event.title}`, - html: ` + try { + await sendEmail({ + to: nextRegistration.user.email, + subject: `Spot Confirmed: ${nextRegistration.event.title}`, + html: `You have been promoted from the waitlist.
Your registration for ${nextRegistration.event.title} is now confirmed.
`, - }); - } catch (_) {} + }); + } catch (_) {} - try { - await createNotification( - nextRegistration.user._id, - 'waitlist_promoted', - `Good news! A spot opened up for ${nextRegistration.event.title}`, - `/events/${nextRegistration.event._id}` - ); - } catch (notifErr) { - console.error('Failed to create waitlist notification:', notifErr); - } - }; + try { + await createNotification( + nextRegistration.user._id, + 'waitlist_promoted', + `Good news! A spot opened up for ${nextRegistration.event.title}`, + `/events/${nextRegistration.event._id}` + ); + } catch (notifErr) { + console.error('Failed to create waitlist notification:', notifErr); + } +}; export const cancelRegistration = async (req, res) => { try { diff --git a/backend/src/routes/registrationRoutes.js b/backend/src/routes/registrationRoutes.js index d514166..215a1c1 100644 --- a/backend/src/routes/registrationRoutes.js +++ b/backend/src/routes/registrationRoutes.js @@ -4,16 +4,11 @@ import { authorizeRoles } from '../middleware/roles.js'; import { registrationRateLimiter } from '../middleware/rateLimiters.js'; import { registerForEvent, myRegistrations, participantsForEvent, checkInParticipant, exportParticipantsCsv, checkRegistrationStatus, cancelRegistration } from '../controllers/registrationController.js'; - - - - - const router = Router(); router.post( '/:id/register', - registrationLimiter, + registrationRateLimiter, authenticate, authorizeRoles('customer', 'organizer', 'admin'), registerForEvent @@ -26,14 +21,6 @@ router.post('/:id/checkin', authenticate, authorizeRoles('customer', 'organizer' router.get('/:id/participants.csv', authenticate, authorizeRoles('customer', 'organizer', 'admin'), exportParticipantsCsv); // End point to cancel registration -router.delete("/:id/cancel",authenticate,cancelRegistration); -// End point to check refund status -router.get("/:id/refund-status",authenticate,checkRefundStatus); -// End point to check refund policy -router.get("/:id/refund-policy",authenticate,checkRefundPolicy) - -// End point to cancel registration -router.delete("/:id/cancel",authenticate,cancelRegistration); - +router.delete("/:id/cancel", authenticate, cancelRegistration); export default router;