Files
cactoide/src/routes/event/[id]/+page.server.ts
2025-11-03 09:04:01 +01:00

173 lines
5.2 KiB
TypeScript

import { database } from '$lib/database/db';
import { events, rsvps } from '$lib/database/schema';
import { eq, asc } from 'drizzle-orm';
import { error, fail } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { logger } from '$lib/logger';
export const load: PageServerLoad = async ({ params, cookies }) => {
const eventId = params.id;
if (!eventId) {
throw error(404, 'EventId not found');
}
try {
// Fetch event and RSVPs in parallel
const [eventData, rsvpData] = await Promise.all([
database.select().from(events).where(eq(events.id, eventId)).limit(1),
database.select().from(rsvps).where(eq(rsvps.eventId, eventId)).orderBy(asc(rsvps.createdAt))
]);
if (!eventData[0]) {
throw error(404, 'Event not found');
}
const event = eventData[0];
const eventRsvps = rsvpData;
// Check if this is an invite-only event
if (event.visibility === 'invite-only') {
// For invite-only events, check if user is the event creator
const userId = cookies.get('cactoideUserId');
if (event.userId !== userId) {
// User is not the creator, redirect to a message about needing invite
throw error(403, 'This event requires an invite link to view');
}
}
// Transform the data to match the expected interface
const transformedEvent = {
id: event.id,
name: event.name,
date: event.date,
time: event.time,
location: event.location,
location_type: event.locationType,
location_url: event.locationUrl,
type: event.type,
attendee_limit: event.attendeeLimit,
visibility: event.visibility,
user_id: event.userId,
created_at: event.createdAt?.toISOString() || new Date().toISOString(),
updated_at: event.updatedAt?.toISOString() || new Date().toISOString()
};
const transformedRsvps = eventRsvps.map((rsvp) => ({
id: rsvp.id,
event_id: rsvp.eventId,
name: rsvp.name,
user_id: rsvp.userId,
created_at: rsvp.createdAt?.toISOString() || new Date().toISOString()
}));
const userId = cookies.get('cactoideUserId');
return {
event: transformedEvent,
rsvps: transformedRsvps,
userId: userId
};
} catch (err) {
if (err instanceof Response) throw err; // This is the 404 error
logger.error({ error: err, eventId }, 'Error loading event');
throw error(500, 'Failed to load event');
}
};
export const actions: Actions = {
addRSVP: async ({ request, params, cookies }) => {
const eventId = params.id;
const formData = await request.formData();
const name = formData.get('newAttendeeName') as string;
const numberOfGuests = parseInt(formData.get('numberOfGuests') as string) || 0;
const userId = cookies.get('cactoideUserId');
if (!name?.trim() || !userId) {
return fail(400, { error: 'Name and user ID are required' });
}
try {
// Check if event exists and get its details
const [eventData] = await database.select().from(events).where(eq(events.id, eventId));
if (!eventData) {
return fail(404, { error: 'Event not found' });
}
// Check if this is an invite-only event
if (eventData.visibility === 'invite-only') {
return fail(403, { error: 'This event requires an invite link to RSVP' });
}
// Get current RSVPs
const currentRSVPs = await database.select().from(rsvps).where(eq(rsvps.eventId, eventId));
// Calculate remaining spots and ensure main attendee + guests fit
const newAttendeesCount = 1 + numberOfGuests;
const remainingSpots = (eventData.attendeeLimit ?? 0) - currentRSVPs.length;
// Check if event is full (for limited type events)
if (eventData.type === 'limited' && eventData.attendeeLimit) {
if (newAttendeesCount > remainingSpots) {
return fail(400, {
error: `Event capacity exceeded. You're trying to add ${newAttendeesCount} attendee${newAttendeesCount === 1 ? '' : 's'} (including yourself), but only ${remainingSpots} spot${remainingSpots === 1 ? '' : 's'} remain.`
});
}
}
// Check if name is already in the list
if (currentRSVPs.some((rsvp) => rsvp.name.toLowerCase() === name.toLowerCase())) {
return fail(400, { error: 'Name already exists for this event' });
}
// Prepare RSVPs to insert
const rsvpsToInsert = [
{
eventId: eventId,
name: name.trim(),
userId: userId,
createdAt: new Date()
}
];
// Add guest entries
for (let i = 1; i <= numberOfGuests; i++) {
rsvpsToInsert.push({
eventId: eventId,
name: `${name.trim()}'s Guest #${i}`,
userId: userId,
createdAt: new Date()
});
}
// Insert all RSVPs
await database.insert(rsvps).values(rsvpsToInsert);
return { success: true, type: 'add' };
} catch (err) {
logger.error({ error: err, eventId, userId, name }, 'Error adding RSVP');
return fail(500, { error: 'Failed to add RSVP' });
}
},
removeRSVP: async ({ request }) => {
const formData = await request.formData();
const rsvpId = formData.get('rsvpId') as string;
if (!rsvpId) {
return fail(400, { error: 'RSVP ID is required' });
}
try {
await database.delete(rsvps).where(eq(rsvps.id, rsvpId));
return { success: true, type: 'remove' };
} catch (err) {
logger.error({ error: err, rsvpId }, 'Error removing RSVP');
return fail(500, { error: 'Failed to remove RSVP' });
}
}
};