Backend work for Smart Room Booking & Clash Detection#233
Backend work for Smart Room Booking & Clash Detection#233gmarav05 wants to merge 1 commit intoOpenLake:mainfrom
Conversation
|
@gmarav05 is attempting to deploy a commit to the openlake's projects Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThis PR introduces a complete room booking management system with new Mongoose models for Room and RoomBooking, a controller with seven route handlers for booking operations including clash prevention and status management, authenticated routes with role-based access control, and initial seed data. Changes
Sequence DiagramsequenceDiagram
actor Client
participant Routes as /api/rooms Routes
participant Controller as roomBookingController
participant Models as Room/RoomBooking Models
participant DB as Database
Client->>Routes: POST /api/rooms/book<br/>(roomId, date, times)
Routes->>Controller: bookRoom(req, res)
Controller->>Models: RoomBooking.find()<br/>(clash check)
Models->>DB: Query pending/approved<br/>bookings
DB-->>Models: Return conflicts
alt Clash Detected
Models-->>Controller: Conflict exists
Controller-->>Routes: 409 Conflict
Routes-->>Client: Booking rejected
else No Clash
Controller->>Models: RoomBooking.create()
Models->>DB: Insert booking
DB-->>Models: New booking ID
Models-->>Controller: Success
Controller-->>Routes: 201 Created
Routes-->>Client: Booking confirmed
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/controllers/roomBookingController.js`:
- Around line 65-66: The populated bookedBy user documents are exposing full
user data; update both populate calls (the one using
RoomBooking.find(...).populate('room event bookedBy') and the similar populate
at lines ~121-122) to restrict fields by using the object form: populate({ path:
'bookedBy', select: '_id name email' }) (or whichever minimal safe fields your
User model exposes), e.g., replace the string 'bookedBy' with the object
selector so only safe user fields are returned while keeping room and event
populations unchanged.
- Around line 19-23: getAllRooms currently returns only static Room docs from
Room.find and must be extended to include live status fields (status,
current_event, occupied_until). After fetching rooms in getAllRooms, retrieve
live occupancy data (from your RoomStatus/Occupancy store or service) for each
room (e.g., via RoomStatus.findOne({ roomId }) or
RoomStatusService.getStatus(room.id)), merge those fields into each room object,
and then send the augmented array with res.json; perform the per-room lookups
concurrently with Promise.all to avoid serial waits and handle missing status
records by supplying sensible defaults.
- Around line 31-39: The clash logic in the RoomBooking.findOne query ignores
the booking date and doesn’t validate time ranges; update the controller to
first validate that startTime < endTime (reject requests where startTime >=
endTime) and then include the booking date in the query (e.g., filter by date or
by combining date+time) so only bookings on the same date are checked; keep the
existing room: roomId and status: { $in: ['Pending','Approved'] } filters and
use the same overlap condition ({ startTime: { $lt: endTime }, endTime: { $gt:
startTime } }) but applied together with date to prevent cross-day matches.
- Around line 77-84: Before updating to 'Approved', load the target booking (use
RoomBooking.findById(id)) to get its room and time window, then if status ===
'Approved' run a query for any other booking with status 'Approved' for the same
room that overlaps the target booking's start/end (e.g. findOne({ _id: { $ne: id
}, room: booking.room, status: 'Approved', $or: [{ start: { $lt: booking.end,
$gte: booking.start } }, { end: { $gt: booking.start, $lte: booking.end } }, {
start: { $lte: booking.start }, end: { $gte: booking.end } }] }) ) and if one
exists return a 409/conflict; only then perform the update
(RoomBooking.findByIdAndUpdate or findOneAndUpdate). Ensure you reference
RoomBooking.findById and RoomBooking.findByIdAndUpdate in the change.
In `@backend/models/schema.js`:
- Around line 648-666: The roomSchema is missing the API-required fields room_id
and allowed_roles; update the mongoose roomSchema to add a room_id field
(String, required, unique — generated if absent via a default function or a
pre('save') hook using UUID logic) and an allowed_roles field (Array of String,
required or with a sensible default like []), and ensure any creation/update
code that constructs Room documents (e.g., create handlers or Model.create
usages) no longer omit room_id so responses match the API contract; keep the
symbol name roomSchema and adjust indexes/validation as needed.
In `@backend/routes/roomBooking.js`:
- Around line 11-13: The route paths are incorrect: change router.get('/rooms',
isAuthenticated, roomBookingController.getAllRooms) to router.get('/',
isAuthenticated, roomBookingController.getAllRooms) so the mounted /api/rooms
yields GET /api/rooms; add a new parameterized route router.get('/:room_id',
isAuthenticated, roomBookingController.getRoomById) (or the appropriate
controller method that returns room details/status) to expose GET
/api/rooms/:room_id; also update the other similar occurrence (the one around
line 35) to use '/' or '/:room_id' instead of duplicating "rooms". Ensure the
referenced controller method name matches an exported function in
roomBookingController.
In `@backend/seed.js`:
- Around line 26-31: The seedRooms function is defined but never invoked, so
sampleRooms are not inserted; call seedRooms from the main seeding flow (for
example inside the top-level async seed/run function or after other seed calls)
or add it to the Promise.all/await chain that runs other seed helpers so it
executes during seeding; locate the seedRooms function and ensure the script
invokes seedRooms() (or exports and calls it where other seeds like
seedUsers/seedBookings are executed) so rooms get inserted on a fresh seed.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
backend/controllers/roomBookingController.jsbackend/index.jsbackend/models/schema.jsbackend/routes/roomBooking.jsbackend/seed.js
| exports.getAllRooms = async (_req, res) => { | ||
| try { | ||
| const rooms = await Room.find({ is_active: true }); | ||
| res.json(rooms); | ||
| } catch (err) { |
There was a problem hiding this comment.
getAllRooms does not return live occupancy/status fields.
The current response returns only static room docs, so it does not satisfy the required live fields (status, current_event, occupied_until) for room status APIs.
💡 Proposed fix (shape)
exports.getAllRooms = async (_req, res) => {
try {
- const rooms = await Room.find({ is_active: true });
- res.json(rooms);
+ const now = new Date();
+ const rooms = await Room.find({ is_active: true }).lean();
+ const activeBookings = await RoomBooking.find({
+ status: 'Approved',
+ startTime: { $lte: now },
+ endTime: { $gte: now },
+ }).populate('event', 'title').lean();
+
+ const activeByRoom = new Map(activeBookings.map(b => [String(b.room), b]));
+ const payload = rooms.map(room => {
+ const active = activeByRoom.get(String(room._id));
+ return {
+ ...room,
+ status: active ? 'occupied' : 'vacant',
+ current_event: active?.event?.title ?? null,
+ occupied_until: active?.endTime ?? null,
+ };
+ });
+ res.json(payload);
} catch (err) {
res.status(500).json({ message: 'Error fetching rooms' });
}
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/roomBookingController.js` around lines 19 - 23,
getAllRooms currently returns only static Room docs from Room.find and must be
extended to include live status fields (status, current_event, occupied_until).
After fetching rooms in getAllRooms, retrieve live occupancy data (from your
RoomStatus/Occupancy store or service) for each room (e.g., via
RoomStatus.findOne({ roomId }) or RoomStatusService.getStatus(room.id)), merge
those fields into each room object, and then send the augmented array with
res.json; perform the per-room lookups concurrently with Promise.all to avoid
serial waits and handle missing status records by supplying sensible defaults.
| const { roomId, eventId, date, startTime, endTime, purpose } = req.body; | ||
| // Check for clash | ||
| const clash = await RoomBooking.findOne({ | ||
| room: roomId, | ||
| status: { $in: ['Pending', 'Approved'] }, | ||
| $or: [ | ||
| { startTime: { $lt: endTime }, endTime: { $gt: startTime } }, | ||
| ], | ||
| }); |
There was a problem hiding this comment.
Clash detection is incorrect: it ignores booking date and accepts invalid time ranges.
This can report clashes across different days and allows startTime >= endTime.
💡 Proposed fix
try {
const { roomId, eventId, date, startTime, endTime, purpose } = req.body;
+ const bookingDate = new Date(date);
+ const start = new Date(startTime);
+ const end = new Date(endTime);
+
+ if ([bookingDate, start, end].some(d => Number.isNaN(d.getTime()))) {
+ return res.status(400).json({ message: 'Invalid date/time format' });
+ }
+ if (start >= end) {
+ return res.status(400).json({ message: 'startTime must be before endTime' });
+ }
+
// Check for clash
const clash = await RoomBooking.findOne({
room: roomId,
+ date: bookingDate,
status: { $in: ['Pending', 'Approved'] },
- $or: [
- { startTime: { $lt: endTime }, endTime: { $gt: startTime } },
- ],
+ startTime: { $lt: end },
+ endTime: { $gt: start },
});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/roomBookingController.js` around lines 31 - 39, The clash
logic in the RoomBooking.findOne query ignores the booking date and doesn’t
validate time ranges; update the controller to first validate that startTime <
endTime (reject requests where startTime >= endTime) and then include the
booking date in the query (e.g., filter by date or by combining date+time) so
only bookings on the same date are checked; keep the existing room: roomId and
status: { $in: ['Pending','Approved'] } filters and use the same overlap
condition ({ startTime: { $lt: endTime }, endTime: { $gt: startTime } }) but
applied together with date to prevent cross-day matches.
| const bookings = await RoomBooking.find(query).populate('room event bookedBy'); | ||
| res.json(bookings); |
There was a problem hiding this comment.
Avoid returning full bookedBy user documents to all authenticated callers.
Both endpoints currently populate full user objects, which can leak internal/sensitive fields unnecessarily.
💡 Proposed fix
-const bookings = await RoomBooking.find(query).populate('room event bookedBy');
+const bookings = await RoomBooking.find(query).populate('room event').populate({
+ path: 'bookedBy',
+ select: '_id role personal_info.name personal_info.email',
+});Also applies to: 121-122
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/roomBookingController.js` around lines 65 - 66, The
populated bookedBy user documents are exposing full user data; update both
populate calls (the one using RoomBooking.find(...).populate('room event
bookedBy') and the similar populate at lines ~121-122) to restrict fields by
using the object form: populate({ path: 'bookedBy', select: '_id name email' })
(or whichever minimal safe fields your User model exposes), e.g., replace the
string 'bookedBy' with the object selector so only safe user fields are returned
while keeping room and event populations unchanged.
| if (!['Approved', 'Rejected'].includes(status)) { | ||
| return res.status(400).json({ message: 'Invalid status' }); | ||
| } | ||
| const booking = await RoomBooking.findByIdAndUpdate( | ||
| id, | ||
| { status, reviewedBy: req.user._id, updated_at: new Date() }, | ||
| { new: true } | ||
| ); |
There was a problem hiding this comment.
Approval should re-check for overlapping approved bookings before status update.
Without a final overlap check on approve, edge/race scenarios can still end in double-approved slots.
💡 Proposed fix
const { id } = req.params;
const { status } = req.body;
@@
+ const current = await RoomBooking.findById(id);
+ if (!current) return res.status(404).json({ message: 'Booking not found' });
+
+ if (status === 'Approved') {
+ const approvedConflict = await RoomBooking.findOne({
+ _id: { $ne: current._id },
+ room: current.room,
+ date: current.date,
+ status: 'Approved',
+ startTime: { $lt: current.endTime },
+ endTime: { $gt: current.startTime },
+ });
+ if (approvedConflict) {
+ return res.status(409).json({ message: 'Cannot approve due to room clash' });
+ }
+ }
+
const booking = await RoomBooking.findByIdAndUpdate(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/roomBookingController.js` around lines 77 - 84, Before
updating to 'Approved', load the target booking (use RoomBooking.findById(id))
to get its room and time window, then if status === 'Approved' run a query for
any other booking with status 'Approved' for the same room that overlaps the
target booking's start/end (e.g. findOne({ _id: { $ne: id }, room: booking.room,
status: 'Approved', $or: [{ start: { $lt: booking.end, $gte: booking.start } },
{ end: { $gt: booking.start, $lte: booking.end } }, { start: { $lte:
booking.start }, end: { $gte: booking.end } }] }) ) and if one exists return a
409/conflict; only then perform the update (RoomBooking.findByIdAndUpdate or
findOneAndUpdate). Ensure you reference RoomBooking.findById and
RoomBooking.findByIdAndUpdate in the change.
| const roomSchema = new mongoose.Schema({ | ||
| name: { | ||
| type: String, | ||
| required: true, | ||
| unique: true, | ||
| }, | ||
| capacity: { | ||
| type: Number, | ||
| required: true, | ||
| }, | ||
| location: { | ||
| type: String, | ||
| required: true, | ||
| }, | ||
| amenities: [ | ||
| { | ||
| type: String, | ||
| }, | ||
| ], |
There was a problem hiding this comment.
Room schema is missing required API fields (room_id, allowed_roles).
The linked API contract expects these fields in room responses, but the model currently does not store them.
💡 Proposed fix
const roomSchema = new mongoose.Schema({
+ room_id: {
+ type: String,
+ required: true,
+ unique: true,
+ index: true,
+ },
name: {
type: String,
required: true,
unique: true,
},
@@
amenities: [
@@
],
+ allowed_roles: [
+ {
+ type: String,
+ },
+ ],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/models/schema.js` around lines 648 - 666, The roomSchema is missing
the API-required fields room_id and allowed_roles; update the mongoose
roomSchema to add a room_id field (String, required, unique — generated if
absent via a default function or a pre('save') hook using UUID logic) and an
allowed_roles field (Array of String, required or with a sensible default like
[]), and ensure any creation/update code that constructs Room documents (e.g.,
create handlers or Model.create usages) no longer omit room_id so responses
match the API contract; keep the symbol name roomSchema and adjust
indexes/validation as needed.
| // Get all rooms | ||
| router.get('/rooms', isAuthenticated, roomBookingController.getAllRooms); | ||
|
|
There was a problem hiding this comment.
Route contract does not match the required room APIs.
With the /api/rooms mount, GET /rooms becomes GET /api/rooms/rooms, and there is no GET /api/rooms/:room_id endpoint for room detail/status.
💡 Proposed fix
-// Get all rooms
-router.get('/rooms', isAuthenticated, roomBookingController.getAllRooms);
+// GET /api/rooms
+router.get('/', isAuthenticated, roomBookingController.getAllRooms);
+
+// GET /api/rooms/:room_id
+router.get('/:room_id', isAuthenticated, roomBookingController.getRoomById);Also applies to: 35-35
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/routes/roomBooking.js` around lines 11 - 13, The route paths are
incorrect: change router.get('/rooms', isAuthenticated,
roomBookingController.getAllRooms) to router.get('/', isAuthenticated,
roomBookingController.getAllRooms) so the mounted /api/rooms yields GET
/api/rooms; add a new parameterized route router.get('/:room_id',
isAuthenticated, roomBookingController.getRoomById) (or the appropriate
controller method that returns room details/status) to expose GET
/api/rooms/:room_id; also update the other similar occurrence (the one around
line 35) to use '/' or '/:room_id' instead of duplicating "rooms". Ensure the
referenced controller method name matches an exported function in
roomBookingController.
| const seedRooms = async () => { | ||
| console.log("Seeding sample rooms..."); | ||
| await Room.deleteMany({}); | ||
| await Room.insertMany(sampleRooms); | ||
| console.log("Sample rooms seeded!"); | ||
| }; |
There was a problem hiding this comment.
seedRooms is never called, so rooms are never seeded.
This leaves the room booking flow without baseline room data after a fresh seed run.
💡 Proposed fix
async function seedDB() {
try {
@@
await clearData();
+ await seedRooms();
await seedOrganizationalUnits();
await seedUsers();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/seed.js` around lines 26 - 31, The seedRooms function is defined but
never invoked, so sampleRooms are not inserted; call seedRooms from the main
seeding flow (for example inside the top-level async seed/run function or after
other seed calls) or add it to the Promise.all/await chain that runs other seed
helpers so it executes during seeding; locate the seedRooms function and ensure
the script invokes seedRooms() (or exports and calls it where other seeds like
seedUsers/seedBookings are executed) so rooms get inserted on a fresh seed.
name: " Pull Request"
about: Propose and submit changes to the project for review
title: "PR: [Brief Description of Changes]"
labels: ""
assignees: harshitap1305, sakshi1755
Related Issue
Changes Introduced
Why This Change?
Screenshots / Video (if applicable)
Testing
npm testin the relevant directory).Documentation Updates
README.mdwith new instructions.Checklist
git checkout -b feature/my-amazing-feature).Deployment Notes
💬 Additional Notes
Summary by CodeRabbit