Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pull Collab service xl #26

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9a155f7
Change the sequence of values for the hostname
Oct 20, 2024
655c1e3
Merge remote-tracking branch 'upstream/collaboration-service' into 79…
whitesnowx Oct 29, 2024
293dbb2
conflict revolve -- no crash but pending fix on deleting residue data…
whitesnowx Oct 29, 2024
bcbe6aa
deleting residue when no more users are in the room
whitesnowx Oct 29, 2024
dba5900
FindingMatch back-navigation revert back
whitesnowx Nov 1, 2024
68db6fb
remove rabbit sample in env sample of collab-service
whitesnowx Nov 1, 2024
54a3d90
Merge pull request #80 from whitesnowx/79_mergeconflict
tsulim Nov 1, 2024
b8ed228
Merge remote-tracking branch 'upstream/main' into 81_deletingSocketRe…
whitesnowx Nov 1, 2024
8ff3220
= to ==
whitesnowx Nov 1, 2024
78510be
attached roomId to socket and add some logging message
whitesnowx Nov 1, 2024
357e7d8
Merge pull request #82 from whitesnowx/81_deletingSocketResidue
tsulim Nov 1, 2024
7f0ef31
Add Code Execution functionality with judge0
xuelinglow Nov 2, 2024
1b7ca3c
Fix language id problem
xuelinglow Nov 2, 2024
18cf88d
Merge branch 'main' of https://github.com/CS3219-AY2425S1/cs3219-ay24…
xuelinglow Nov 3, 2024
cd8b313
Add submit button to collab service
xuelinglow Nov 4, 2024
f22d658
Merge branch 'checking_branch_2' of https://github.com/AndrewOng2066/…
Nov 5, 2024
8f601bc
Merge branch 'checking_branch_2' into collab-service-xl
AndrewOng2066 Nov 5, 2024
faf9faa
Merge branch 'nth_branch_testing' into collab-service-xl
AndrewOng2066 Nov 5, 2024
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: 0 additions & 3 deletions backend/collaboration-service/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,5 @@ PORT=5003
# Firebase
PRIVATE_KEY=

# Rabbit MQ
RABBIT_PASSWORD=

# Service URL
URL_QUESTION_SERVICE=
68 changes: 68 additions & 0 deletions backend/collaboration-service/controller/collabController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
const axios = require("axios");

/**
* POST /add
*
* Creates questions from form data and store in firebase
*
* Responses:
* - 500: Server error if something goes wrong while fetching data.
*/
const submitCode = async (req, res) => {
const { code, languageId } = req.body;

if (typeof code !== "string" || !languageId) {
return res.status(400).json({ error: "Invalid request data" });
}

const formData = {
language_id: languageId,
source_code: btoa(code),
};
const options = {
method: "POST",
url: process.env.REACT_APP_RAPID_API_URL,
params: { base64_encoded: "true", fields: "*" },
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
data: formData,
};
axios
.request(options)
.then(function (response) {
console.log("res.data", response.data);
return res.status(200).json({ submissionId: response.data.token });
})
.catch((err) => {
let error = err.response ? err.response.data : err;
console.log(error);
});
};

const getSubmissionResult = async (req, res) => {
const submissionId = req.params.submissionId;
const options = {
method: "GET",
url: process.env.REACT_APP_RAPID_API_URL + "/" + submissionId,
params: { base64_encoded: "true", fields: "*" },
headers: {
"X-RapidAPI-Host": process.env.REACT_APP_RAPID_API_HOST,
"X-RapidAPI-Key": process.env.REACT_APP_RAPID_API_KEY,
},
};
try {
let response = await axios.request(options);
return res.status(200).json(response.data);
} catch (err) {
console.error("Error fetching submission result:", err);
let error = err.response
? err.response.data
: { message: "Internal Server Error" };
return res.status(500).json(error); // Handle errors appropriately
}
};

module.exports = { submitCode, getSubmissionResult };
191 changes: 128 additions & 63 deletions backend/collaboration-service/handler/socketHandler.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
// Author(s): Xue Ling, Xiu Jia, Calista, Andrew
const { getRandomQuestion, getComplexity } = require("../service/questionService");
// Author(s): Xue Ling, Xiu Jia, Calista
const {
getRandomQuestion,
getComplexity,
} = require("../service/questionService");

const db = require("../config/firebase");

let socketMap = {};
let intervalMap = {};

let latestContentText = {};
let latestContentCode = {};
let latestLanguage = {}
let latestLanguage = {};
let haveNewData = {};
let activeUserInRoom = {}; // track user details in rooms
let submitStatus = {};
let activeIdInRoom = {}; //track socket ids in rooms

const handleSocketIO = (io) => {
io.on("connection", (socket) => {
console.log(`A user connected with socket ID: ${socket.id}`);


socket.on("createSocketRoom", async ({ data, id, currentUser }) => {
// Store the socket id for the user
socketMap[currentUser] = socket.id;
socket.roomId = id;

socket.join(id);
console.log(`User with socket ID ${socket.id} joined room with ID ${id}`);
if (!activeIdInRoom[id]) {
activeIdInRoom[id] = []; // Initialize as an empty array
}
activeIdInRoom[id].push(socket.id);

console.log(`UactiveUserInRoom[id]: ${activeIdInRoom[id].length}`);

const room = io.sockets.adapter.rooms.get(id);

Expand All @@ -29,17 +42,32 @@ const handleSocketIO = (io) => {

const complexity = getComplexity(user1, user2);

const questionData = await getRandomQuestion(user1.category, complexity);
const questionData = await getRandomQuestion(
user1.category,
complexity
);

activeUserInRoom[id] = {
user1: user1,
user2: user2,
questionData: questionData,
length: 2,
};

io.in(id).emit("readyForCollab", {
id: id,
user1,
user2,
questionData
questionData,
});

console.log(`Room ${id} is ready. Collaboration question sent: ${questionData}`);
if (!submitStatus[id]) {
submitStatus[id] = { user1: false, user2: false };
}

console.log(
`Room ${id} is ready. Collaboration question sent: ${questionData}`
);



Expand Down Expand Up @@ -97,45 +125,9 @@ const handleSocketIO = (io) => {

// a timer to backup the current collab data
const interval = setInterval(async () => {
const currentTime = new Date().toISOString();
const currentContentText = latestContentText[id];
const currentContentCode = latestContentCode[id];
const currentLanguage = latestLanguage[id] || null;
const periodicData = {
user1,
user2,
questionData,
currentLanguage,
currentContentText,
currentContentCode,
timestamp: currentTime
};

try {
const collabRef = db.collection("collabs").doc(id);
const doc = await collabRef.get();

if (doc.exists) {
if (haveNewData[id]) {
haveNewData[id] = false;
await collabRef.update(periodicData);
console.log(`Collab Data updated to Firebase at ${currentTime}`);
}
} else {

await collabRef.set({
roomId: id,
...periodicData
});
console.log(`New Collab page recorded to Firebase at ${currentTime}`);
}

} catch (error) {
console.error("Fail to save to database: ", error);
}
updateCollabData(id);
}, 5000);


intervalMap[id] = interval;
}
});
Expand All @@ -159,31 +151,104 @@ const handleSocketIO = (io) => {
socket.to(id).emit("languageChange", { language });
});

// Handle disconnection
socket.on("disconnect", () => {
// Delete the
if (intervalMap[socket.id]) {
clearInterval(intervalMap[socket.id]);
delete intervalMap[socket.id];
}
if (socket.roomId) {
delete latestContentText[socket.roomId];
delete latestContentCode[socket.roomId];
delete latestLanguage[socket.roomId];
delete haveNewData[socket.roomId];
// Handle submission
socket.on("endSession", () => {
if (socket.id === activeIdInRoom[socket.roomId][0]) {
submitStatus[socket.roomId].user1 = true;
} else if (socket.id === activeIdInRoom[socket.roomId][1]) {
submitStatus[socket.roomId].user2 = true;
}
for (let user in socketMap) {
if (socketMap[user] === socket.id) {
delete socketMap[user];
break;
}
console.log(submitStatus);
if (submitStatus[socket.roomId].user1 && submitStatus[socket.roomId].user2) {
console.log("Both users have submitted in room:", socket.roomId);
updateCollabData(socket.roomId);
activeUserInRoom[socket.roomId].length = 0;
io.to(socket.roomId).emit('sessionEnded');
socket.disconnect();
}
console.log(`User with socket ID ${socket.id} disconnected`);
});

// Handle disconnection
socket.on("disconnect", () => {
console.log("disconnecting...");
const activeUsers = activeUserInRoom[socket.roomId];
if (!activeUsers || activeUsers.length === 0) {
console.log(`No active users in room: ${socket.roomId}`);
} else {
activeUserInRoom[socket.roomId].length =
Math.max(0, activeUserInRoom[socket.roomId].length - 1);;

if (activeUserInRoom[socket.roomId].length == 0) {
console.log(
`All users in roomId ${socket.roomId} disconnected, deleting room data`
);
delete activeUserInRoom[socket.roomId];
delete activeIdInRoom[socket.roomId];

clearInterval(intervalMap[socket.roomId]);
delete intervalMap[socket.roomId];
delete latestContentText[socket.roomId];
delete latestContentCode[socket.roomId];
delete latestLanguage[socket.roomId];
delete haveNewData[socket.roomId];
}

for (let user in socketMap) {
if (socketMap[user] === socket.id) {
delete socketMap[user];
break;
}
}
console.log(
`User with socket ID ${socket.id} disconnected, leaving ${socket.roomId}`
);
}
});
});
};

async function updateCollabData(id) {
const currentTime = new Date().toISOString();
const currentContentText = latestContentText[id];
const currentContentCode = latestContentCode[id];
const currentLanguage = latestLanguage[id] || null;
const user1 = activeUserInRoom[id].user1;
const user2 = activeUserInRoom[id].user2;
const questionData = activeUserInRoom[id].questionData;
const periodicData = {
user1,
user2,
questionData,
currentLanguage,
currentContentText,
currentContentCode,
timestamp: currentTime,
};

try {
const collabRef = db.collection("collabs").doc(id);
const doc = await collabRef.get();

if (doc.exists) {
if (haveNewData[id]) {
haveNewData[id] = false;
await collabRef.update(periodicData);
console.log(
`Collab Data for roomid ${id} updated to Firebase at ${currentTime}`
);
}
} else {
await collabRef.set({
roomId: id,
...periodicData,
});
console.log(
`New Collab page for roomid ${id} recorded to Firebase at ${currentTime}`
);
}
} catch (error) {
console.error("Fail to save to database: ", error);
}
}
// Export user functions
module.exports = { handleSocketIO };
module.exports = { handleSocketIO };
19 changes: 19 additions & 0 deletions backend/collaboration-service/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const http = require("http");
const { Server } = require("socket.io");
const port = process.env.PORT || 5003;

const collaborationRoute = require("./routes/collaborationRoute");

// Import the socket handler
const { handleSocketIO } = require("./handler/socketHandler.js");

Expand All @@ -25,4 +27,21 @@ handleSocketIO(io); // This calls the function to set up the socket listeners
// Start the server
server.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

const express = require("express");
const cors = require("cors");
const app = express();

app.use(cors());
app.use(express.json());

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Routes
app.use("/collaboration", collaborationRoute);

app.listen(5004, () => {
console.log(`Example app listening on port ${5004}`);
});
Loading