// server/api/events/[id]/rsvp.post.ts import { createNocoDBEventsClient } from '~/server/utils/nocodb-events'; import { getMemberByKeycloakId } from '~/server/utils/nocodb'; import { createSessionManager } from '~/server/utils/session'; import type { EventRSVPRequest } from '~/utils/types'; export default defineEventHandler(async (event) => { try { const eventId = getRouterParam(event, 'id'); const body = await readBody(event) as EventRSVPRequest; if (!eventId) { throw createError({ statusCode: 400, statusMessage: 'Event ID is required' }); } // Get user session using the proper SessionManager const sessionManager = createSessionManager(); const cookieHeader = getHeader(event, 'cookie'); const session = sessionManager.getSession(cookieHeader); if (!session || !session.user) { throw createError({ statusCode: 401, statusMessage: 'Authentication required' }); } const eventsClient = createNocoDBEventsClient(); // Get the event details const eventDetails = await eventsClient.findOne(eventId); if (!eventDetails) { throw createError({ statusCode: 404, statusMessage: 'Event not found' }); } // Check if event is active if (eventDetails.status !== 'active') { throw createError({ statusCode: 400, statusMessage: 'Cannot RSVP to inactive events' }); } // Check if event is in the past const eventDate = new Date(eventDetails.start_datetime); const now = new Date(); if (eventDate < now) { throw createError({ statusCode: 400, statusMessage: 'Cannot RSVP to past events' }); } // Get member details for pricing logic const member = await getMemberByKeycloakId(session.user.id); if (!member) { throw createError({ statusCode: 404, statusMessage: 'Member record not found' }); } // Generate payment reference const paymentReference = generatePaymentReference(member.member_id || member.Id); // Determine pricing and payment status let paymentStatus: 'pending' | 'not_required' | 'paid' | 'overdue' = 'not_required'; let isMemberPricing = 'false'; if (eventDetails.is_paid === 'true' && body.rsvp_status === 'confirmed') { paymentStatus = 'pending'; // Check if member qualifies for member pricing const isDuesCurrent = member.current_year_dues_paid === 'true'; const memberPricingEnabled = eventDetails.member_pricing_enabled === 'true'; if (isDuesCurrent && memberPricingEnabled) { isMemberPricing = 'true'; } } // Validate guest count if event allows guests const extraGuests = parseInt((body as any).extra_guests || '0'); if (extraGuests > 0) { if (eventDetails.guests_permitted !== 'true') { throw createError({ statusCode: 400, statusMessage: 'This event does not allow guests' }); } const maxGuestsAllowed = parseInt(eventDetails.max_guests_permitted || '0'); if (extraGuests > maxGuestsAllowed) { throw createError({ statusCode: 400, statusMessage: `Maximum ${maxGuestsAllowed} guests allowed per person` }); } } // Check event capacity including guests if (eventDetails.max_attendees && body.rsvp_status === 'confirmed') { const maxCapacity = parseInt(eventDetails.max_attendees); const currentAttendees = parseInt(eventDetails.current_attendees || '0'); const totalRequested = 1 + extraGuests; // Member + guests if (currentAttendees + totalRequested > maxCapacity) { // Auto-set to waitlist if over capacity body.rsvp_status = 'waitlist'; console.log(`[RSVP] Event at capacity, placing on waitlist: ${currentAttendees} + ${totalRequested} > ${maxCapacity}`); } } // Create RSVP record const rsvpData = { record_type: 'rsvp', event_id: eventId, member_id: member.member_id || member.Id, rsvp_status: body.rsvp_status, payment_status: paymentStatus, payment_reference: paymentReference, attended: 'false', rsvp_notes: body.rsvp_notes || '', extra_guests: extraGuests.toString(), is_member_pricing: isMemberPricing, created_at: new Date().toISOString(), updated_at: new Date().toISOString() }; const newRSVP = await eventsClient.createRSVP(rsvpData); // Update event attendee count using the new force update method try { const newAttendeeCount = await eventsClient.forceUpdateEventAttendeeCount(eventId); console.log('[RSVP] ✅ Force updated event attendee count for event:', eventId, 'to:', newAttendeeCount); } catch (countError) { console.log('[RSVP] ⚠️ Failed to force update attendee count:', countError); // Don't fail the RSVP creation if count update fails } // Include payment information in response for paid events let responseData: any = newRSVP; if (eventDetails.is_paid === 'true' && paymentStatus === 'pending') { const registrationConfig = await getRegistrationConfig(); responseData = { ...newRSVP, payment_info: { cost: isMemberPricing === 'true' ? eventDetails.cost_members : eventDetails.cost_non_members, iban: registrationConfig.iban, recipient: registrationConfig.accountHolder, reference: paymentReference, member_pricing: isMemberPricing === 'true' } }; } return { success: true, data: responseData, message: (body.rsvp_status as string) === 'waitlist' ? 'Added to waitlist - event is full' : 'RSVP submitted successfully' }; } catch (error: any) { console.error('Error processing RSVP:', error); if (error.statusCode) { throw error; } throw createError({ statusCode: 500, statusMessage: 'Failed to process RSVP' }); } }); // Helper functions function generatePaymentReference(memberId: string): string { const date = new Date().toISOString().split('T')[0]; return `EVT-${memberId}-${date}`; } async function getRegistrationConfig() { // This should fetch from your admin config system return { iban: 'FR76 1234 5678 9012 3456 7890 123', accountHolder: 'MonacoUSA Association' }; } async function updateEventAttendeeCount(eventId: string) { console.log('[updateEventAttendeeCount] Updating attendee count for event:', eventId); try { const eventsClient = createNocoDBEventsClient(); // Get all confirmed RSVPs for this event const confirmedRSVPs = await eventsClient.getEventRSVPs(eventId, 'confirmed'); // Calculate total attendees (confirmed RSVPs + their guests) let totalAttendees = 0; for (const rsvp of confirmedRSVPs) { totalAttendees += 1; // The member themselves const guestCount = parseInt(rsvp.extra_guests || '0'); totalAttendees += guestCount; // Add their guests } console.log('[updateEventAttendeeCount] Calculated total attendees:', totalAttendees, 'from', confirmedRSVPs.length, 'RSVPs'); // Update the event's current_attendees field await eventsClient.update(eventId, { current_attendees: totalAttendees.toString() }); console.log('[updateEventAttendeeCount] ✅ Successfully updated event attendee count to:', totalAttendees); return totalAttendees; } catch (error) { console.error('[updateEventAttendeeCount] ❌ Error updating attendee count:', error); throw error; } }