Skip to content

Backend work for Smart Room Booking & Clash Detection#233

Open
gmarav05 wants to merge 1 commit intoOpenLake:mainfrom
gmarav05:main
Open

Backend work for Smart Room Booking & Clash Detection#233
gmarav05 wants to merge 1 commit intoOpenLake:mainfrom
gmarav05:main

Conversation

@gmarav05
Copy link

@gmarav05 gmarav05 commented Feb 25, 2026


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

  • Added: Room and RoomBooking schemas, controller, and routes for room management, booking, clash detection, and status updates.
  • Fixed: N/A
  • Updated: Registered new routes in backend server, added sample room seeding logic.
  • Removed: N/A

Why This Change?

  • Problem: No centralized room booking system; manual clash detection was error-prone.
  • Solution: Introduces robust backend for booking rooms, automatic clash detection, and admin approval workflow.
  • Impact: Streamlines event coordination, prevents double-booking, and improves admin workflow.

Screenshots / Video (if applicable)

Before After

Testing

  • Ran unit tests and all passed (npm test in the relevant directory).
  • Manually tested the following scenarios:
    • Test Case 1: [Describe steps + Expected Result]
    • Test Case 2: [Describe steps + Expected Result]
  • Tested on different browsers (Chrome, Firefox) for UI changes.
  • Verified there are no new console warnings or errors.

Documentation Updates

  • Updated the README.md with new instructions.
  • Added clear code comments where logic is complex.
  • N/A

Checklist

  • I have created a new branch for this PR (git checkout -b feature/my-amazing-feature).
  • I have starred the repository.
  • My code follows the project's coding style and conventions.
  • My commit messages are clear and follow the project's guidelines.
  • I have performed a self-review of my own code.
  • I have added tests that prove my fix is effective or that my feature works.
  • All new and existing tests passed locally with my changes.
  • This PR introduces no breaking changes (or they are clearly documented).

Deployment Notes

  • Requires a database migration/schema update.
  • Requires new environment variables to be set.
  • N/A

💬 Additional Notes

  • [Add any other context or information here.]

Summary by CodeRabbit

  • New Features
    • Room management: Create and view available rooms with capacity and location details.
    • Room booking: Reserve rooms with date and time slots featuring automatic conflict detection.
    • Availability checking: View existing bookings for specific dates and rooms.
    • Booking administration: Approve or reject pending room reservations.
    • Booking cancellation: Cancel existing reservations with role-based permissions.

@vercel
Copy link

vercel bot commented Feb 25, 2026

@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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Walkthrough

This 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

Cohort / File(s) Summary
Data Models
backend/models/schema.js
Added Room schema (name, capacity, location, amenities, is_active, timestamps) and RoomBooking schema (references to Room/User, date, time fields, status enum, indexed on room/date/time) with corresponding Mongoose models.
Controller & Routes
backend/controllers/roomBookingController.js, backend/routes/roomBooking.js
Implemented seven controller methods (createRoom, getAllRooms, bookRoom with clash detection, getAvailability, updateBookingStatus, cancelBooking, getBookings) exposed via authenticated Express routes with role-based authorization for admin-only operations.
Integration & Seeding
backend/index.js, backend/seed.js
Mounted new room booking routes at /api/rooms endpoint; added Room model import and seedRooms function with sample room data (not invoked in main seedDB flow).

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Rooms now book without a fuss,
With clash detection just for us!
Availability at a glance so clear,
Status updates throughout the year! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR implements room booking, clash detection, and status updates, but diverges from issue #231's requirements: does not provide GET /room or GET /room/:room_id endpoints returning room status (vacant/occupied), occupancy detection based on current time, or event/booking queries as specified. Implement the required endpoints (GET /room and GET /room/:room_id) with live occupancy status detection, current_event mapping, and proper response schemas as defined in issue #231.
Out of Scope Changes check ❓ Inconclusive All changes align with general room booking infrastructure (schemas, controller, routes, seeding). However, the implementation diverges from the specific scope of issue #231, which requires room status/availability APIs with occupancy detection rather than booking management and clash detection. Clarify whether this PR should address issue #231's room status APIs or if it is scoped to a separate room booking feature. If addressing #231, refocus on occupancy detection and status endpoints.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed Title clearly summarizes the main change: introducing backend functionality for room booking with clash detection, which aligns with the primary objective of the changeset.
Description check ✅ Passed Description follows the template with all major sections completed: Related Issue (#231), Changes Introduced, Why This Change, and relevant checklists. However, Testing and Documentation sections lack specific details and remain mostly unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 64a42c1 and a0bccbc.

📒 Files selected for processing (5)
  • backend/controllers/roomBookingController.js
  • backend/index.js
  • backend/models/schema.js
  • backend/routes/roomBooking.js
  • backend/seed.js

Comment on lines +19 to +23
exports.getAllRooms = async (_req, res) => {
try {
const rooms = await Room.find({ is_active: true });
res.json(rooms);
} catch (err) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +31 to +39
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 } },
],
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +65 to +66
const bookings = await RoomBooking.find(query).populate('room event bookedBy');
res.json(bookings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +77 to +84
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 }
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +648 to +666
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,
},
],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +11 to +13
// Get all rooms
router.get('/rooms', isAuthenticated, roomBookingController.getAllRooms);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +26 to +31
const seedRooms = async () => {
console.log("Seeding sample rooms...");
await Room.deleteMany({});
await Room.insertMany(sampleRooms);
console.log("Sample rooms seeded!");
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FOSSOVERFLOW-25] feat: Implement Room Status & Details APIs

1 participant