import { NextRequest, NextResponse } from 'next/server'; import { db } from '@/lib/db'; import { bookings, courts, timeSlots } from '@/lib/db/schema'; import { eq, and } from 'drizzle-orm'; import { getSession } from '@/lib/session'; import { logActivity, ACTIONS, ENTITY_TYPES } from '@/lib/activity-logger'; export async function GET(request: NextRequest) { try { const session = await getSession(); if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const userBookings = await db .select({ id: bookings.id, courtId: bookings.courtId, date: bookings.date, startTime: bookings.startTime, endTime: bookings.endTime, status: bookings.status, notes: bookings.notes, createdAt: bookings.createdAt, court: { id: courts.id, name: courts.name, }, }) .from(bookings) .innerJoin(courts, eq(bookings.courtId, courts.id)) .where(eq(bookings.userId, session.userId)); return NextResponse.json({ bookings: userBookings }); } catch (error) { console.error('Error fetching bookings:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } export async function POST(request: NextRequest) { try { const session = await getSession(); if (!session) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const { courtId, date, timeSlot } = await request.json(); if (!courtId || !date || !timeSlot) { return NextResponse.json( { error: 'Missing required fields: courtId, date, timeSlot', }, { status: 400 } ); } // Parse timeSlot (e.g., "14:00") to get start and end times const startTime = timeSlot; const [hours, minutes] = timeSlot.split(':').map(Number); const endTime = `${String(hours + 1).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`; // Validate booking date is not in the past const bookingDate = new Date(date); const today = new Date(); today.setHours(0, 0, 0, 0); if (bookingDate < today) { return NextResponse.json( { error: 'Cannot book dates in the past', }, { status: 400 } ); } // Check if court exists and is active const court = await db.select().from(courts).where(eq(courts.id, courtId)).limit(1); if (court.length === 0 || !court[0].isActive) { return NextResponse.json( { error: 'Court not found or inactive', }, { status: 400 } ); } // CRITICAL: Validate that booking is allowed for this day and time const dayOfWeek = bookingDate.getDay(); const availableTimeSlots = await db .select() .from(timeSlots) .where( and( eq(timeSlots.dayOfWeek, dayOfWeek), eq(timeSlots.isActive, true) ) ); // Check if any time slots are configured for this day if (availableTimeSlots.length === 0) { return NextResponse.json( { error: `No bookings are allowed on ${['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek]}s. The facility is closed on this day.`, }, { status: 400 } ); } // Check if the requested time slot is within any of the allowed time ranges const requestedHour = parseInt(startTime.split(':')[0]); const isTimeSlotValid = availableTimeSlots.some(slot => { const slotStartHour = parseInt(slot.startTime.split(':')[0]); const slotEndHour = parseInt(slot.endTime.split(':')[0]); return requestedHour >= slotStartHour && requestedHour < slotEndHour; }); if (!isTimeSlotValid) { const allowedRanges = availableTimeSlots.map(slot => `${slot.startTime}-${slot.endTime}`).join(', '); return NextResponse.json( { error: `Time slot ${startTime} is not available on ${['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek]}s. Available times: ${allowedRanges}`, }, { status: 400 } ); } // Check if slot is already booked const existingBooking = await db .select() .from(bookings) .where( and( eq(bookings.courtId, courtId), eq(bookings.date, date), eq(bookings.startTime, startTime), eq(bookings.status, 'active') ) ) .limit(1); if (existingBooking.length > 0) { return NextResponse.json( { error: 'Time slot already booked', }, { status: 400 } ); } // Create the booking const [newBooking] = await db .insert(bookings) .values({ id: crypto.randomUUID(), userId: session.userId, courtId, date, startTime, endTime, status: 'active', createdAt: new Date(), updatedAt: new Date(), }) .returning(); // Log the activity await logActivity({ userId: session.userId, action: ACTIONS.BOOKING_CREATE, entityType: ENTITY_TYPES.BOOKING, entityId: newBooking.id, details: { courtId, courtName: court[0].name, date, startTime, endTime, }, request, }); return NextResponse.json({ booking: newBooking, message: 'Booking created successfully', }); } catch (error) { console.error('Error creating booking:', error); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } }