Skip to content

feat(facilities): add room booking with a database-enforced clash guard (fixes #297) - #302

Open
MOHITKOURAV01 wants to merge 1 commit into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-297-facility-booking
Open

feat(facilities): add room booking with a database-enforced clash guard (fixes #297)#302
MOHITKOURAV01 wants to merge 1 commit into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-297-facility-booking

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Related Issue

Closes #297

Description

Shared spaces — the auditorium, the labs, the sports hall, the seminar room — are booked in a paper diary, and the diary cannot stop two people writing on it an hour apart. This adds a facility register and a booking system whose central guarantee is that the double booking is impossible rather than unlikely.

The clash guard is one conditional update

Bookings are embedded in the facility document, and that is the design decision the whole module rests on. It lets "this room is free at that time" be expressed as the filter of a single findOneAndUpdate:

bookings: {
  $not: {
    $elemMatch: {
      date,
      status: { $in: ['pending', 'approved'] },
      startMinute: { $lt: guardedEnd },
      endMinute:   { $gt: guardedStart },
    },
  },
}

"This facility has no live booking on that date whose interval intersects mine." If two requests race, the second one's filter no longer matches and it gets a 409. A read-then-write version lets both pass the check before either writes, which is the paper diary reimplemented in JavaScript.

I considered a separate Booking collection, which is the more conventional shape. It needs either a transaction — this project does not assume a replica set — or a unique index over a discretised time grid, which forces every booking onto fixed 30-minute boundaries. Embedding gives a real guarantee on a plain mongod, and a booking is never queried without its facility.

The buffer is part of the guarded interval

Every facility carries bufferMinutes. The interval the database protects is the requested window widened by the buffer on each side, so the chairs going out and the chairs coming back are enforced rather than written in a note somebody is supposed to read. A hall booked 14:00–15:00 with a 30-minute buffer genuinely blocks 13:30–15:30, and the availability view shows only gaps large enough to survive it.

The rest

  • A pending request holds the room. Approving a request whose slot was taken while it sat in the queue is worse than a short wait, and it is what people expect from a booking system.
  • Approval is only required where the facility says so. Making a teacher wait for permission to use a spare classroom is how a booking system gets abandoned for the diary it replaced.
  • Closing a facility for maintenance leaves its existing bookings visible and returns them in the response, so somebody has to deal with each one rather than discovering the closure on the morning.
  • Editing opening hours is refused if it would strand a future booking outside them — otherwise the rules themselves create the double-use.
  • Deleting a facility is refused while future bookings are held on it; retiring is the intended path.
  • Utilisation is reported as a share of the hours each room is actually open, because a count of bookings says nothing about a hall booked once for eight hours.

Pages / Components Added or Modified

  • backend/models/Facility.js — new
  • backend/controllers/facilityController.js — new
  • backend/routes/facilityRoutes.js — new
  • frontend/src/pages/FacilityBooking.jsx — new
  • backend/server.js — mounts /api/facilities (additions only)
  • frontend/src/App.jsx — adds the /facilities route (additions only)

Screenshots

/facilities opens on availability: pick a date and optionally a window, and every room answers free or taken in one screen. Each room is drawn as a day bar with its booked intervals laid over it, scaled to that room's own opening hours rather than a fixed 24-hour axis, with the free gaps below it as clickable chips. Admins get the approval queue and the facility register on the same page.

Images omitted because the view needs seeded facilities to be worth looking at — happy to add them.

Checklist

  • npm run lint — 18 problems, identical to main, all pre-existing and outside this PR's files
  • npx vite build succeeds
  • node --check clean on every backend file added or changed
  • Responsive layout verified (filters wrap, the day bar scales, the booking dialog scrolls)
  • No console errors or warnings
  • Additions-only in the shared files, on anchors no other open PR uses; merged against main and each of my other open PRs to confirm no conflict

Bookings are embedded in the facility document so that no-overlap is
expressible as the filter of one findOneAndUpdate: the update only applies
when the facility holds no live booking on that date whose interval
intersects the requested one. Two people booking the same hall at the same
instant means the second matches nothing and gets a 409, on a plain mongod
with no transaction and no discretised time grid.

The guarded interval is the requested window widened by the facility's setup
buffer on each side, so clear-down time is protected by the database rather
than written in a note. A pending request holds the room, because approving
a request whose slot was taken while it queued is worse than a short wait.

Availability answers what is free on a date - per room, with the gaps large
enough to book - and the register refuses to shrink opening hours under a
booking that would be left outside them.

Closes Sitaram8472#297
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.

[Feature]: Facility & room booking with database-enforced clash prevention

1 participant