Skip to content
Merged
126 changes: 126 additions & 0 deletions backend/controllers/roomBookingController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@

const { Room, RoomBooking, Event, User } = require('../models/schema');

exports.createRoom = async (req, res) => {
try {
const { name, capacity, location, amenities } = req.body;
const room = new Room({ name, capacity, location, amenities });
await room.save();
res.status(201).json({ message: 'Room created', room });
} catch (err) {
if (err.code === 11000) {
return res.status(409).json({ message: 'Room name already exists' });
}
res.status(500).json({ message: 'Error creating room', error: err.message });
}
};


exports.getAllRooms = async (_req, res) => {
try {
const rooms = await Room.find({ is_active: true });
res.json(rooms);
} catch (err) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
res.status(500).json({ message: 'Error fetching rooms' });
}
};


exports.bookRoom = async (req, res) => {
try {
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 } },
],
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (clash) {
return res.status(409).json({ message: 'Room clash detected', conflictingBooking: clash });
}
const booking = new RoomBooking({
room: roomId,
event: eventId,
date,
startTime,
endTime,
purpose,
bookedBy: req.user._id,
});
await booking.save();
res.status(201).json({ message: 'Room booked (pending approval)', booking });
} catch (err) {
res.status(500).json({ message: 'Error booking room', error: err.message });
}
};


exports.getAvailability = async (req, res) => {
try {
const { date, roomId } = req.query;
const query = { date: new Date(date) };
if (roomId) query.room = roomId;
const bookings = await RoomBooking.find(query).populate('room event bookedBy');
res.json(bookings);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} catch (err) {
res.status(500).json({ message: 'Error fetching availability' });
}
};


exports.updateBookingStatus = async (req, res) => {
try {
const { id } = req.params;
const { status } = req.body;
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 }
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (!booking) return res.status(404).json({ message: 'Booking not found' });
res.json({ message: 'Booking status updated', booking });
} catch (err) {
res.status(500).json({ message: 'Error updating booking status' });
}
};


exports.cancelBooking = async (req, res) => {
try {
const { id } = req.params;
const booking = await RoomBooking.findById(id);
if (!booking) return res.status(404).json({ message: 'Booking not found' });

if (
String(booking.bookedBy) !== String(req.user._id) &&
!['PRESIDENT', 'GENSEC_SCITECH', 'GENSEC_ACADEMIC', 'GENSEC_CULTURAL', 'GENSEC_SPORTS', 'CLUB_COORDINATOR'].includes(req.user.role)
) {
return res.status(403).json({ message: 'Forbidden' });
}
booking.status = 'Cancelled';
booking.updated_at = new Date();
await booking.save();
res.json({ message: 'Booking cancelled', booking });
} catch (err) {
res.status(500).json({ message: 'Error cancelling booking' });
}
};

exports.getBookings = async (req, res) => {
try {
const { roomId, date, status } = req.query;
const query = {};
if (roomId) query.room = roomId;
if (date) query.date = new Date(date);
if (status) query.status = status;
const bookings = await RoomBooking.find(query).populate('room event bookedBy');
res.json(bookings);
} catch (err) {
res.status(500).json({ message: 'Error fetching bookings' });
}
};
2 changes: 2 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const dashboardRoutes = require("./routes/dashboard.js");

const analyticsRoutes = require("./routes/analytics.js");
const porRoutes = require("./routes/por.js");
const roomBookingRoutes = require("./routes/roomBooking.js");
const app = express();

if (process.env.NODE_ENV === "production") {
Expand Down Expand Up @@ -68,6 +69,7 @@ app.use("/api/announcements", announcementRoutes);
app.use("/api/dashboard", dashboardRoutes);
app.use("/api/announcements", announcementRoutes);
app.use("/api/analytics", analyticsRoutes);
app.use("/api/rooms", roomBookingRoutes);
app.use("/api/por", porRoutes);

// Start the server
Expand Down
90 changes: 90 additions & 0 deletions backend/models/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,94 @@ const OrganizationalUnit = mongoose.model(
);
const Announcement = mongoose.model("Announcement", announcementSchema);

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,
},
],
Comment thread
coderabbitai[bot] marked this conversation as resolved.
is_active: {
type: Boolean,
default: true,
},
created_at: {
type: Date,
default: Date.now,
},
updated_at: {
type: Date,
default: Date.now,
},
});

const Room = mongoose.model("Room", roomSchema);

const roomBookingSchema = new mongoose.Schema({
room: {
type: mongoose.Schema.Types.ObjectId,
ref: "Room",
required: true,
},
event: {
type: mongoose.Schema.Types.ObjectId,
ref: "Event",
},
date: {
type: Date,
required: true,
},
startTime: {
type: Date,
required: true,
},
endTime: {
type: Date,
required: true,
},
purpose: {
type: String,
},
bookedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
status: {
type: String,
enum: ["Pending", "Approved", "Rejected", "Cancelled"],
default: "Pending",
},
reviewedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
created_at: {
type: Date,
default: Date.now,
},
updated_at: {
type: Date,
default: Date.now,
},
});

roomBookingSchema.index({ room: 1, date: 1, startTime: 1, endTime: 1 });

const RoomBooking = mongoose.model("RoomBooking", roomBookingSchema);

module.exports = {
User,
Feedback,
Expand All @@ -656,4 +744,6 @@ module.exports = {
Position,
OrganizationalUnit,
Announcement,
Room,
RoomBooking,
};
35 changes: 35 additions & 0 deletions backend/routes/roomBooking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const express = require('express');
const router = express.Router();
const isAuthenticated = require('../middlewares/isAuthenticated');
const authorizeRole = require('../middlewares/authorizeRole');
const { ROLE_GROUPS, ROLES } = require('../utils/roles');
const roomBookingController = require('../controllers/roomBookingController');

// Create a new room (admin only)
router.post('/create-room', isAuthenticated, authorizeRole(ROLE_GROUPS.ADMIN), roomBookingController.createRoom);

// Get all rooms
router.get('/rooms', isAuthenticated, roomBookingController.getAllRooms);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
// Book a room (admin only)
router.post('/book', isAuthenticated, authorizeRole(ROLE_GROUPS.ADMIN), roomBookingController.bookRoom);

// Get room availability
router.get('/availability', isAuthenticated, roomBookingController.getAvailability);

// Get bookings (filterable)
router.get('/bookings', isAuthenticated, roomBookingController.getBookings);

// Update booking status (approve/reject)
router.put('/bookings/:id/status', isAuthenticated, authorizeRole([
ROLES.PRESIDENT,
ROLES.GENSEC_SCITECH,
ROLES.GENSEC_ACADEMIC,
ROLES.GENSEC_CULTURAL,
ROLES.GENSEC_SPORTS,
]), roomBookingController.updateBookingStatus);

// Cancel a booking
router.delete('/bookings/:id', isAuthenticated, roomBookingController.cancelBooking);

module.exports = router;
35 changes: 26 additions & 9 deletions backend/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,34 @@ require("dotenv").config();
const mongoose = require("mongoose");

const {
User,
Feedback,
Achievement,
UserSkill,
Skill,
Event,
PositionHolder,
Position,
OrganizationalUnit,
User,
Feedback,
Achievement,
UserSkill,
Skill,
Event,
PositionHolder,
Position,
OrganizationalUnit,
Room,
} = require("./models/schema");

// Sample Rooms for Seeding
const sampleRooms = [
{ name: "LH-101", capacity: 60, location: "Academic Block 1, Ground Floor", amenities: ["Projector", "AC", "Whiteboard"] },
{ name: "LH-102", capacity: 60, location: "Academic Block 1, Ground Floor", amenities: ["Projector", "AC"] },
{ name: "Seminar Hall", capacity: 120, location: "Admin Block, 1st Floor", amenities: ["Projector", "Sound System", "AC"] },
];

// Seeds sample rooms for testing room booking features.

const seedRooms = async () => {
console.log("Seeding sample rooms...");
await Room.deleteMany({});
await Room.insertMany(sampleRooms);
console.log("Sample rooms seeded!");
};
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// --- Data for Seeding ---

// Original club/committee data.
Expand Down