diff --git a/api/modules/bookings/bookingRepository.js b/api/modules/bookings/bookingRepository.js index 3bb4d55..a02d256 100644 --- a/api/modules/bookings/bookingRepository.js +++ b/api/modules/bookings/bookingRepository.js @@ -107,26 +107,42 @@ export async function createBooking({ userId, deskId, date }) { till.setUTCHours(19, 0, 0, 0); const toDate = till.toISOString(); - const { rows } = await db.query( - sql` - WITH new_booking AS ( - INSERT INTO booking (user_id, desk_id, from_date, to_date) - VALUES ($1, $2, $3, $4) - RETURNING *) - SELECT - b.id AS booking_id, - b.desk_id, b.user_id, - b.from_date, b.to_date, - u.first_name, u.last_name, - d.name AS desk_name - FROM - new_booking b - JOIN "user" u ON b.user_id = u.id - JOIN desk d ON b.desk_id = d.id - `, - [userId, deskId, date, toDate], - ); - return rows[0]; + try { + const { rows } = await db.query( + sql` + WITH new_booking AS ( + INSERT INTO booking (user_id, desk_id, from_date, to_date) + VALUES ($1, $2, $3, $4) + ON CONFLICT DO NOTHING + RETURNING *) + SELECT + b.id AS booking_id, + b.desk_id, b.user_id, + b.from_date, b.to_date, + u.first_name, u.last_name, + d.name AS desk_name + FROM + new_booking b + JOIN "user" u ON b.user_id = u.id + JOIN desk d ON b.desk_id = d.id + `, + [userId, deskId, date, toDate], + ); + + // If no rows returned, it means there was a conflict + if (rows.length === 0) { + return null; + } + + return rows[0]; + } catch (error) { + // Handle constraint violation errors + if (error.code === "23514") { + // Check constraint violation + return null; + } + throw error; + } } export async function getFilteredBookings({ from, to, userId }) { diff --git a/api/modules/bookings/bookingService.js b/api/modules/bookings/bookingService.js index 0058874..82d72a4 100644 --- a/api/modules/bookings/bookingService.js +++ b/api/modules/bookings/bookingService.js @@ -32,7 +32,18 @@ export async function handleCreateBooking({ userId, deskId, date }) { StatusCodes.CONFLICT, ); } - return await createBooking({ userId, deskId, date: date }); + + const booking = await createBooking({ userId, deskId, date: date }); + + // If createBooking returns null, it means there was a conflict + if (!booking) { + throw new ApiError( + "Desk is already booked for this date or time range.", + StatusCodes.CONFLICT, + ); + } + + return booking; } export async function getBookingById(id) { diff --git a/script.sql b/script.sql index fbc97c5..671e3cd 100644 --- a/script.sql +++ b/script.sql @@ -3,6 +3,9 @@ CREATE DATABASE deskeando; \c deskeando; +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + CREATE TABLE "desk"( id uuid default uuid_generate_v4() PRIMARY KEY, "name" text not null @@ -24,6 +27,10 @@ CREATE TABLE "booking"( "to_date" timestamp with time zone not null ); +-- Prevent two users from booking the same desk at the same time +-- This ensures only one booking per desk per date +CREATE UNIQUE INDEX unique_desk_date ON booking (desk_id, from_date); + INSERT INTO "desk" (id, name) VALUES ('b142a09d-76f7-4140-a401-52a7bc5f22c5','Desk 1'),