Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 30 additions & 18 deletions backend/src/controllers/roomController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Request, Response } from "express";
import mongoose from "mongoose";
import Room from "../models/roomModel.js";
import type { IRoom } from "../models/roomModel.js";
import mongoose from "mongoose";
import { io } from "../server.js";


// Extend Express Request with userId
declare global {
namespace Express {
interface Request {
Expand All @@ -17,17 +17,25 @@ declare global {
export const createRoom = async (req: Request, res: Response) => {
try {
const { name } = req.body;
const userId = req.userId;

if (!name) return res.status(400).json({ message: "Room name is required" });
if (!req.userId) return res.status(401).json({ message: "Unauthorized" });
if (!userId) return res.status(401).json({ message: "Unauthorized" });

const existingRoom = await Room.findOne({ name });
if (existingRoom)
return res.status(400).json({ message: "Room with this name already exists" });

const room: IRoom = new Room({ name, members: [req.userId] });
const room: IRoom = new Room({
name,
members: [userId],
host: userId,
isActive: true,
});

await room.save();

io.emit("room-created", { roomId: room._id, name: room.name });
io.emit("room-created", { roomId: room._id.toString(), name: room.name });
res.status(201).json(room);
} catch (error: any) {
console.error("Create room error:", error.message);
Expand All @@ -38,7 +46,7 @@ export const createRoom = async (req: Request, res: Response) => {
// ---------------------- List Rooms ----------------------
export const listRooms = async (_req: Request, res: Response) => {
try {
const rooms = await Room.find().populate("members", "username email");
const rooms = await Room.find({ isActive: true }).populate("members", "username email");
res.json(rooms);
} catch (error: any) {
console.error("List rooms error:", error.message);
Expand All @@ -52,18 +60,16 @@ export const joinRoom = async (req: Request, res: Response) => {
const roomIdOrName = req.params.roomIdOrName;
const userId = req.userId;

if (!roomIdOrName) return res.status(400).json({ message: "Room name or ID is required" });
if (!roomIdOrName)
return res.status(400).json({ message: "Room name or ID is required" });
if (!userId) return res.status(401).json({ message: "Unauthorized - Missing userId" });

let room = await Room.findOne({ name: roomIdOrName });
if (!room && mongoose.Types.ObjectId.isValid(roomIdOrName)) {
room = await Room.findById(roomIdOrName);
}

if (!room) {
console.error("Join room error: Room not found for", roomIdOrName);
return res.status(404).json({ message: "Room not found" });
}
if (!room) return res.status(404).json({ message: "Room not found" });

const userObjId = new mongoose.Types.ObjectId(userId);
if (!room.members.some(m => m.equals(userObjId))) {
Expand All @@ -72,12 +78,14 @@ export const joinRoom = async (req: Request, res: Response) => {
}

const updatedRoom = await Room.findById(room._id).populate("members", "username email");
io.to(room._id.toString()).emit("user-joined", { userId, roomId: room._id });
io.to(room._id.toString()).emit("update-members", updatedRoom?.members || []);

const roomIdStr = room._id.toString();
io.to(roomIdStr).emit("user-joined", { userId, roomId: roomIdStr });
io.to(roomIdStr).emit("update-members", updatedRoom?.members || []);

res.json(updatedRoom);
} catch (error: any) {
console.error("Join room error:", error.message, error.stack);
console.error("Join room error:", error.message);
res.status(500).json({ message: "Server error", error: error.message });
}
};
Expand All @@ -87,6 +95,8 @@ export const leaveRoom = async (req: Request, res: Response) => {
try {
const { roomId } = req.params;
const userId = req.userId;

if (!roomId) return res.status(400).json({ message: "Missing room ID" });
if (!userId) return res.status(401).json({ message: "Unauthorized" });

const room = await Room.findById(roomId);
Expand All @@ -104,7 +114,8 @@ export const leaveRoom = async (req: Request, res: Response) => {
io.to(roomId).emit("update-members", updatedRoom?.members || []);

if (room.members.length === 0) {
await Room.findByIdAndDelete(roomId);
room.isActive = false;
await room.save();
io.to(roomId).emit("room-ended", { roomId, reason: "empty" });
io.socketsLeave(roomId);
}
Expand All @@ -128,11 +139,12 @@ export const endRoom = async (req: Request, res: Response) => {
const room = await Room.findById(roomId);
if (!room) return res.status(404).json({ message: "Room not found" });

const hostId = room.members[0]?.toString();
if (hostId !== userId)
if (room.host.toString() !== userId)
return res.status(403).json({ message: "Only the host can end the room" });

await Room.findByIdAndDelete(roomId);
room.isActive = false;
await room.save();

io.to(roomId).emit("room-ended", { roomId, endedBy: userId });
io.socketsLeave(roomId);

Expand Down
1 change: 1 addition & 0 deletions backend/src/models/roomModel.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import mongoose, { Schema, Document } from "mongoose";

export interface IRoom extends Document {
_id: mongoose.Types.ObjectId;
name: string;
members: mongoose.Types.ObjectId[];
host: mongoose.Types.ObjectId; // 👈 identifies who created/owns the room
Expand Down
53 changes: 53 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"axios": "^1.12.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^12.23.24",
"lucide-react": "^0.545.0",
"next-themes": "^0.4.6",
"react": "^19.2.0",
Expand Down
42 changes: 23 additions & 19 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,34 @@ import OAuthSuccess from "./pages/OAuthSuccess.js";
import RoomActions from "./pages/RoomActions.js";
import ErrorBoundary from "./components/ErrorBoundary.js";
import "./index.css"
import CreateRoom from "./pages/CreateRoom.js";
import JoinRoom from "./pages/JoinRoom.js";
import InRoom from "./pages/InRoom.js";
import CreateRoomLobby from "./pages/CreateRoomLobby.js";
import CreateRoom from "./pages/CreateRoom.js";
const queryClient = new QueryClient();

const App = () => (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<SignIn />} />
<Route path="/room-actions" element={<RoomActions />} />
<Route path="/oauth-success" element={<OAuthSuccess />} />
<Route path="/create-room" element={<CreateRoom/>} /> {/* ✅ */}
<Route path="/join-room" element={<JoinRoom />} /> {/* ✅ */}
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
</ErrorBoundary>
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<SignIn />} />
<Route path="/room-actions" element={<RoomActions />} />
<Route path="/oauth-success" element={<OAuthSuccess />} />
<Route path="/create-room" element={<CreateRoom />} /> {/* ✅ */}
<Route path="/join-room" element={<JoinRoom />} /> {/* ✅ */}
<Route path="/room/:roomName" element={<InRoom />} />
<Route path="/lobby/:roomId" element={<CreateRoomLobby />} />
</Routes>
</BrowserRouter>
</TooltipProvider>
</QueryClientProvider>
</ErrorBoundary>
);

export default App;
Loading
Loading