Skip to content

Commit 689a6ec

Browse files
Merge branch 'main' into main
2 parents f2d0c5a + c52b33a commit 689a6ec

File tree

12 files changed

+508
-195
lines changed

12 files changed

+508
-195
lines changed

backend/src/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ app.use(express.json());
1515
app.use(cookieParser()); // <-- Add this middleware HERE
1616
app.use(
1717
cors({
18-
origin: process.env.FRONTEND_URL || "http://localhost:5173",
18+
origin: process.env.FRONTEND_URL || "http://localhost:5174",
1919
credentials: true,
20+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
2021
})
2122
);
2223
//initialize passport
Lines changed: 120 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,144 @@
11
import type { Request, Response } from "express";
22
import Room from "../models/roomModel.js";
33
import type { IRoom } from "../models/roomModel.js";
4-
import * as express from "express";
54
import mongoose from "mongoose";
5+
import { io } from "../server.js";
6+
67

78
declare global {
8-
namespace Express {
9-
interface Request {
10-
userId?: string; // or number, depending on your ID type
11-
}
9+
namespace Express {
10+
interface Request {
11+
userId?: string;
1212
}
13+
}
1314
}
1415

15-
// Create a new room
16+
// ---------------------- Create Room ----------------------
1617
export const createRoom = async (req: Request, res: Response) => {
17-
try {
18-
const { name } = req.body;
19-
if (!name) return res.status(400).json({ message: "Room name is required" });
20-
21-
const existingRoom = await Room.findOne({ name });
22-
if (existingRoom) return res.status(400).json({ message: "Room already exists" });
23-
24-
const room: IRoom = new Room({ name, members: [req.userId] });
25-
await room.save();
26-
res.status(201).json(room);
27-
} catch (error) {
28-
res.status(500).json({ message: "Server error", error });
29-
}
18+
try {
19+
const { name } = req.body;
20+
if (!name) return res.status(400).json({ message: "Room name is required" });
21+
if (!req.userId) return res.status(401).json({ message: "Unauthorized" });
22+
23+
const existingRoom = await Room.findOne({ name });
24+
if (existingRoom)
25+
return res.status(400).json({ message: "Room with this name already exists" });
26+
27+
const room: IRoom = new Room({ name, members: [req.userId] });
28+
await room.save();
29+
30+
io.emit("room-created", { roomId: room._id, name: room.name });
31+
res.status(201).json(room);
32+
} catch (error: any) {
33+
console.error("Create room error:", error.message);
34+
res.status(500).json({ message: "Server error", error: error.message });
35+
}
3036
};
3137

32-
// List all rooms
38+
// ---------------------- List Rooms ----------------------
3339
export const listRooms = async (_req: Request, res: Response) => {
34-
try {
35-
const rooms = await Room.find().populate("members", "username email");
36-
res.json(rooms);
37-
} catch (error) {
38-
res.status(500).json({ message: "Server error", error });
39-
}
40+
try {
41+
const rooms = await Room.find().populate("members", "username email");
42+
res.json(rooms);
43+
} catch (error: any) {
44+
console.error("List rooms error:", error.message);
45+
res.status(500).json({ message: "Server error", error: error.message });
46+
}
4047
};
4148

42-
// Join a room
49+
// ---------------------- Join Room ----------------------
4350
export const joinRoom = async (req: Request, res: Response) => {
44-
try {
45-
const { roomId } = req.params;
46-
const room = await Room.findById(roomId);
47-
if (!room) return res.status(404).json({ message: "Room not found" });
48-
const userId = new mongoose.Types.ObjectId(req.userId!);
49-
if (!room.members.includes(userId)) {
50-
room.members.push(userId);
51-
await room.save();
52-
}
53-
54-
res.json(room);
55-
} catch (error) {
56-
res.status(500).json({ message: "Server error", error });
51+
try {
52+
const roomIdOrName = req.params.roomIdOrName;
53+
const userId = req.userId;
54+
55+
if (!roomIdOrName) return res.status(400).json({ message: "Room name or ID is required" });
56+
if (!userId) return res.status(401).json({ message: "Unauthorized - Missing userId" });
57+
58+
let room = await Room.findOne({ name: roomIdOrName });
59+
if (!room && mongoose.Types.ObjectId.isValid(roomIdOrName)) {
60+
room = await Room.findById(roomIdOrName);
61+
}
62+
63+
if (!room) {
64+
console.error("Join room error: Room not found for", roomIdOrName);
65+
return res.status(404).json({ message: "Room not found" });
5766
}
67+
68+
const userObjId = new mongoose.Types.ObjectId(userId);
69+
if (!room.members.some(m => m.equals(userObjId))) {
70+
room.members.push(userObjId);
71+
await room.save();
72+
}
73+
74+
const updatedRoom = await Room.findById(room._id).populate("members", "username email");
75+
io.to(room._id.toString()).emit("user-joined", { userId, roomId: room._id });
76+
io.to(room._id.toString()).emit("update-members", updatedRoom?.members || []);
77+
78+
res.json(updatedRoom);
79+
} catch (error: any) {
80+
console.error("Join room error:", error.message, error.stack);
81+
res.status(500).json({ message: "Server error", error: error.message });
82+
}
5883
};
5984

60-
// Leave a room
85+
// ---------------------- Leave Room ----------------------
6186
export const leaveRoom = async (req: Request, res: Response) => {
62-
try {
63-
const { roomId } = req.params;
64-
const room = await Room.findById(roomId);
65-
if (!room) return res.status(404).json({ message: "Room not found" });
87+
try {
88+
const { roomId } = req.params;
89+
const userId = req.userId;
90+
if (!userId) return res.status(401).json({ message: "Unauthorized" });
91+
92+
const room = await Room.findById(roomId);
93+
if (!room) return res.status(404).json({ message: "Room not found" });
94+
95+
const wasMember = room.members.some(m => m.toString() === userId);
96+
if (!wasMember)
97+
return res.status(400).json({ message: "You are not a member of this room" });
6698

67-
room.members = room.members.filter(member => member.toString() !== req.userId);
68-
await room.save();
99+
room.members = room.members.filter(m => m.toString() !== userId);
100+
await room.save();
69101

70-
res.json({ message: "Left room successfully", room });
71-
} catch (error) {
72-
res.status(500).json({ message: "Server error", error });
102+
io.to(roomId).emit("user-left", { userId, roomId });
103+
const updatedRoom = await Room.findById(roomId).populate("members", "username email");
104+
io.to(roomId).emit("update-members", updatedRoom?.members || []);
105+
106+
if (room.members.length === 0) {
107+
await Room.findByIdAndDelete(roomId);
108+
io.to(roomId).emit("room-ended", { roomId, reason: "empty" });
109+
io.socketsLeave(roomId);
73110
}
111+
112+
res.json({ message: "Left room successfully" });
113+
} catch (error: any) {
114+
console.error("Leave room error:", error.message);
115+
res.status(500).json({ message: "Server error", error: error.message });
116+
}
117+
};
118+
119+
// ---------------------- End Room (Host Only) ----------------------
120+
export const endRoom = async (req: Request, res: Response) => {
121+
try {
122+
const { roomId } = req.params;
123+
const userId = req.userId;
124+
125+
if (!roomId) return res.status(400).json({ message: "Missing room ID" });
126+
if (!userId) return res.status(401).json({ message: "Unauthorized" });
127+
128+
const room = await Room.findById(roomId);
129+
if (!room) return res.status(404).json({ message: "Room not found" });
130+
131+
const hostId = room.members[0]?.toString();
132+
if (hostId !== userId)
133+
return res.status(403).json({ message: "Only the host can end the room" });
134+
135+
await Room.findByIdAndDelete(roomId);
136+
io.to(roomId).emit("room-ended", { roomId, endedBy: userId });
137+
io.socketsLeave(roomId);
138+
139+
res.json({ message: "Room ended successfully" });
140+
} catch (error: any) {
141+
console.error("End room error:", error.message);
142+
res.status(500).json({ message: "Server error", error: error.message });
143+
}
74144
};

backend/src/models/roomModel.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,31 @@
11
import mongoose, { Schema, Document } from "mongoose";
22

33
export interface IRoom extends Document {
4-
name: string;
5-
members: mongoose.Types.ObjectId[];
6-
createdAt: Date;
4+
name: string;
5+
members: mongoose.Types.ObjectId[];
6+
host: mongoose.Types.ObjectId; // 👈 identifies who created/owns the room
7+
isActive: boolean; // 👈 track whether the room is still active
8+
createdAt: Date;
79
}
810

911
const roomSchema: Schema = new Schema({
10-
name: { type: String, required: true, unique: true },
11-
members: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
12-
createdAt: { type: Date, default: Date.now },
12+
name: { type: String, required: true, unique: true },
13+
members: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }],
14+
host: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, // host reference
15+
isActive: { type: Boolean, default: true }, // true until ended or empty
16+
createdAt: { type: Date, default: Date.now },
1317
});
1418

19+
// ✅ Optional cleanup or logic
20+
// When all members leave, mark as inactive automatically (not delete immediately)
21+
roomSchema.methods.deactivateIfEmpty = async function () {
22+
if (this.members.length === 0) {
23+
this.isActive = false;
24+
await this.save();
25+
}
26+
};
27+
28+
// Optional: auto-remove inactive rooms after certain time
29+
// roomSchema.index({ createdAt: 1 }, { expireAfterSeconds: 7 * 24 * 60 * 60 }); // expires in 7 days
30+
1531
export default mongoose.model<IRoom>("Room", roomSchema);

backend/src/routes/roomRoutes.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
import { Router } from "express";
2-
import { createRoom, listRooms, joinRoom, leaveRoom } from "../controllers/roomController.js";
2+
import {
3+
createRoom,
4+
listRooms,
5+
joinRoom,
6+
leaveRoom,
7+
endRoom,
8+
} from "../controllers/roomController.js";
39
import { protect } from "../middleware/authMiddleware.js";
410

511
const router = Router();
612

713
// All routes require authentication
814
router.use(protect);
915

10-
router.post("/createRoom", createRoom); // Create room
11-
router.get("/listRooms", listRooms); // List all rooms
12-
router.post("/:roomId/join", joinRoom); // Join a room
13-
router.post("/:roomId/leave", leaveRoom); // Leave a room
16+
router.post("/createRoom", createRoom);
17+
router.get("/listRooms", listRooms);
18+
router.post("/:roomIdOrName/join", joinRoom);
19+
router.post("/:roomId/leave", leaveRoom);
20+
router.post("/:roomId/end", endRoom);
1421

1522
export default router;

0 commit comments

Comments
 (0)