Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions app/api/events/[id]/register/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { NextRequest, NextResponse } from "next/server";
import { Types } from "mongoose";
import connectToDatabase from "@/lib/mongodb";
import { Booking, Event } from "@/database";
import { revalidatePath } from "next/cache";

interface RouteParams {
params: Promise<{ id: string }>;
}

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export async function POST(req: NextRequest, { params }: RouteParams) {
try {
const { id: eventId } = await params;

// 1. Validate event ID
if (!eventId || !Types.ObjectId.isValid(eventId)) {
return NextResponse.json(
{ message: "Invalid event ID format." },
{ status: 400 }
);
}

// 2. Parse request body
let body;
try {
body = await req.json();
} catch {
return NextResponse.json(
{ message: "Invalid request body." },
{ status: 400 }
);
}

const { email } = body;

// 3. Validate email presence and format
if (!email || typeof email !== "string" || !email.trim()) {
return NextResponse.json(
{ message: "Email is required." },
{ status: 400 }
);
}

const cleanEmail = email.trim().toLowerCase();

if (!EMAIL_REGEX.test(cleanEmail)) {
return NextResponse.json(
{ message: "Please provide a valid email address." },
{ status: 400 }
);
}

// 4. Connect to database
await connectToDatabase();

// 5. Verify if the event exists
const event = await Event.findById(eventId);
if (!event) {
return NextResponse.json(
{ message: "Event not found." },
{ status: 404 }
);
}

// 6. Check for existing registration
const existingRegistration = await Booking.findOne({
eventId,
email: cleanEmail,
});

if (existingRegistration) {
return NextResponse.json(
{ message: "You have already registered for this event." },
{ status: 409 }
);
}

// 7. Save registration (create booking)
const booking = await Booking.create({
eventId,
email: cleanEmail,
});

// 8. Revalidate paths to refresh cache
revalidatePath(`/events/${event.slug}`);
revalidatePath("/");

return NextResponse.json(
{ message: "Registration successful", booking },
{ status: 201 }
);
} catch (error: any) {
Comment thread
TarunyaProgrammer marked this conversation as resolved.
// 9. Handle duplicate key errors from unique compound index
if (error && error.code === 11000) {
return NextResponse.json(
{ message: "You have already registered for this event." },
{ status: 409 }
);
}

console.error("Error in registration API:", error);
return NextResponse.json(
{ message: "Internal server error.", error: error instanceof Error ? error.message : "Unknown" },
{ status: 500 }
);
}
}
Comment thread
TarunyaProgrammer marked this conversation as resolved.
134 changes: 72 additions & 62 deletions lib/actions/booking.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,86 @@ import { Booking } from "@/database";
import { revalidatePath } from "next/cache";

interface CreateBookingParams {
eventId: string;
slug: string;
email: string;
eventId: string;
slug: string;
email: string;
}

export async function createBooking({ eventId, slug, email }: CreateBookingParams) {
try {
await connectToDatabase();
const cleanEmail = email.toLowerCase().trim();

// Create new booking
const booking = await Booking.create({
eventId,
email: cleanEmail,
});

// Revalidate caches after booking
revalidatePath(`/events/${slug}`);
revalidatePath("/");

return { success: true, booking: JSON.parse(JSON.stringify(booking)) };
} catch (error: unknown) {
if (typeof error === "object" && error !== null && "code" in error && error.code === 11000) {
return { success: false, error: 'You have already booked this event' };
}
console.error('Error creating booking:', error);
return { success: false, error: 'Failed to create booking' };
try {
await connectToDatabase();
const cleanEmail = email.toLowerCase().trim();

// Check for existing booking to prevent duplicates
const existingBooking = await Booking.findOne({
eventId,
email: cleanEmail,
});

if (existingBooking) {
return { success: false, error: 'You have already booked this event' };
}

// Create new booking
const booking = await Booking.create({
eventId,
email: cleanEmail,
});

// Revalidate caches after booking
revalidatePath(`/events/${slug}`);
revalidatePath("/");

return { success: true, booking: JSON.parse(JSON.stringify(booking)) };
} catch (error: unknown) {
if (typeof error === "object" && error !== null && "code" in error && error.code === 11000) {
return { success: false, error: 'You have already booked this event' };
}
console.error('Error creating booking:', error);
return { success: false, error: 'Failed to create booking' };
}
}

export async function getBookingsByEventId(eventId: string, page = 1, limit = 50) {
try {
await connectToDatabase();

const safePage = Math.max(1, isNaN(Number(page)) ? 1 : Number(page));
const safeLimit = Math.min(100, Math.max(1, isNaN(Number(limit)) ? 50 : Number(limit)));
const skip = (safePage - 1) * safeLimit;

const [bookings, total] = await Promise.all([
Booking.find({ eventId }).skip(skip).limit(safeLimit),
Booking.countDocuments({ eventId })
]);

const totalPages = Math.ceil(total / safeLimit);

return {
success: true,
bookings: JSON.parse(JSON.stringify(bookings)),
page: safePage,
limit: safeLimit,
total,
totalPages
};
} catch (error) {
console.error('Error fetching bookings:', error);
return { success: false, error: 'Failed to fetch bookings' };
}
try {
await connectToDatabase();

const safePage = Math.max(1, isNaN(Number(page)) ? 1 : Number(page));
const safeLimit = Math.min(100, Math.max(1, isNaN(Number(limit)) ? 50 : Number(limit)));
const skip = (safePage - 1) * safeLimit;

const [bookings, total] = await Promise.all([
Booking.find({ eventId }).skip(skip).limit(safeLimit),
Booking.countDocuments({ eventId })
]);

const totalPages = Math.ceil(total / safeLimit);

return {
success: true,
bookings: JSON.parse(JSON.stringify(bookings)),
page: safePage,
limit: safeLimit,
total,
totalPages
};
} catch (error) {
console.error('Error fetching bookings:', error);
return { success: false, error: 'Failed to fetch bookings' };
}
}

export async function getBookingsCountByEventId(eventId: string) {
try {
await connectToDatabase();
try {
await connectToDatabase();

const count = await Booking.countDocuments({ eventId });
const count = await Booking.countDocuments({ eventId });

return { success: true, count };
} catch (error) {
console.error('Error fetching booking count:', error);
return { success: false, error: 'Failed to fetch booking count' };
}
return { success: true, count };
} catch (error) {
console.error('Error fetching booking count:', error);
return { success: false, error: 'Failed to fetch booking count' };
}
}

// Add these function blocks to the very bottom of booking.actions.ts
Expand All @@ -84,18 +94,18 @@ export async function getBookingsByEmail(email: string, page = 1, limit = 50) {

// Clean string formats to match registry criteria
const cleanEmail = email.toLowerCase().trim();

const safePage = Math.max(1, isNaN(Number(page)) ? 1 : Number(page));
const safeLimit = Math.min(100, Math.max(1, isNaN(Number(limit)) ? 50 : Number(limit)));
const skip = (safePage - 1) * safeLimit;

// Fetch user bookings and populate referenced Event model properties
const bookings = await Booking.find({ email: cleanEmail })
.populate('eventId')
.populate('eventId')
.sort({ createdAt: -1 })
.skip(skip)
.limit(safeLimit);

return { success: true, bookings: JSON.parse(JSON.stringify(bookings)) };
} catch (error) {
console.error("Fetch bookings server action failed:", error);
Expand Down
Loading
Loading