diff --git a/Backend/MatchingService/Dockerfile b/Backend/MatchingService/Dockerfile
new file mode 100644
index 0000000000..4a25a56749
--- /dev/null
+++ b/Backend/MatchingService/Dockerfile
@@ -0,0 +1,11 @@
+FROM node:20
+
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+
+EXPOSE 3003
+CMD ["npm", "start"]
diff --git a/Backend/MatchingService/app.js b/Backend/MatchingService/app.js
index 82efb1fff0..66be1da456 100644
--- a/Backend/MatchingService/app.js
+++ b/Backend/MatchingService/app.js
@@ -1,28 +1,32 @@
const express = require('express');
const cors = require("cors");
const dotenv = require("dotenv");
-//const matchmakingRouter = require("./controllers/matchmaking");
-//const { consumeQueue } = require('./rabbitmq/subscriber');
-//const { setupRabbitMQ } = require('./rabbitmq/setup');
-//const { publishToQueue } = require('./rabbitmq/publisher')
+const matchmakingRouter = require("./controllers/matchmaking");
+const { consumeQueue, consumeDLQ } = require('./rabbitmq/subscriber');
+const { setupRabbitMQ } = require('./rabbitmq/setup');
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
+app.options("*", cors());
app.use('/api/match', matchmakingRouter);
// TODO: Start consuming RabbitMQ queues
-/*
+
setupRabbitMQ().then(() => {
consumeQueue().catch(console.error);
- publishToQueue("user_234", "easy", "python")
- publishToQueue("user_100", "easy", "java")
+ consumeDLQ().catch(console.error);
+
+ // publishToQueue({userId: "user_1", difficulty: "easy", language: "java"})
+ // publishToQueue({userId: "user_2", difficulty: "easy", language: "python"})
+ // publishToQueue({userId: "user_3", difficulty: "easy", language: "java"})
})
-*/
+
module.exports = app;
\ No newline at end of file
diff --git a/Backend/MatchingService/controllers/matchmaking.js b/Backend/MatchingService/controllers/matchmaking.js
index 74090d790b..b907fff795 100644
--- a/Backend/MatchingService/controllers/matchmaking.js
+++ b/Backend/MatchingService/controllers/matchmaking.js
@@ -1,17 +1,18 @@
-// TODO: WRITE API FOR MATCHING USER, REMEMBER TO DEAL WITH CORS ALLOW ACCESS ORIGIN ERROR
+// WRITE API FOR MATCHING USER, REMEMBER TO DEAL WITH CORS ALLOW ACCESS ORIGIN ERROR
+// Cors settled in app.js
+
-/*
const express = require('express');
const router = express.Router();
const { publishToQueue } = require('../rabbitmq/publisher');
// Route for frontend to send user matching info
-router.post('/match', async (req, res) => {
- const { userId, language, difficulty } = req.body;
+router.post('/enterMatchmaking', async (req, res) => {
+ const { userId, difficulty, language } = req.body;
try {
// Publish user info to RabbitMQ
- await publishToQueue(userId, language, difficulty);
+ await publishToQueue({userId: userId, difficulty: difficulty, language: language});
res.status(200).send('User info sent for matching.');
} catch (error) {
console.error('Error publishing user info:', error);
@@ -19,5 +20,18 @@ router.post('/match', async (req, res) => {
}
});
-module.exports = router;
-*/
\ No newline at end of file
+// This is for the alternative where the player also listens to a queue after entering matchmaking
+/*
+router.post('/waitMatch', async (req, res) => {
+ try {
+ // Start consuming RabbitMQ queues
+ // await consumeQueue();
+ res.status(200).send('Waiting for match...');
+ } catch (error) {
+ console.error('Error consuming RabbitMQ queue:', error);
+ res.status(500).send('Error in matchmaking process.');
+ }
+})
+ */
+
+module.exports = router;
\ No newline at end of file
diff --git a/Backend/MatchingService/package-lock.json b/Backend/MatchingService/package-lock.json
index f672f6f0a9..e84a30d900 100644
--- a/Backend/MatchingService/package-lock.json
+++ b/Backend/MatchingService/package-lock.json
@@ -8,7 +8,8 @@
"amqplib": "^0.10.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
- "express": "^4.21.1"
+ "express": "^4.21.1",
+ "ws": "^8.18.0"
}
},
"node_modules/@acuminous/bitsyntax": {
@@ -889,6 +890,26 @@
"engines": {
"node": ">= 0.8"
}
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/Backend/MatchingService/package.json b/Backend/MatchingService/package.json
index c522c7b1d1..7c26162f52 100644
--- a/Backend/MatchingService/package.json
+++ b/Backend/MatchingService/package.json
@@ -3,6 +3,11 @@
"amqplib": "^0.10.4",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
- "express": "^4.21.1"
+ "express": "^4.21.1",
+ "ws": "^8.18.0"
+ },
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node index.js"
}
}
diff --git a/Backend/MatchingService/rabbitmq/publisher.js b/Backend/MatchingService/rabbitmq/publisher.js
index 8d86a27942..97b9eb9c33 100644
--- a/Backend/MatchingService/rabbitmq/publisher.js
+++ b/Backend/MatchingService/rabbitmq/publisher.js
@@ -1,36 +1,63 @@
const amqp = require('amqplib');
-// TODO: Write function to publish to rabbitMQ
+const { matching_exchange_name } = require('./setup.js');
+
+let channel = null; // Store a persistent channel connection
+
+async function connectToRabbitMQ() {
+ if (!channel) {
+ try {
+ const connection = await amqp.connect(process.env.RABBITMQ_URL);
+ channel = await connection.createChannel();
+ console.log("RabbitMQ channel created");
+ } catch (error) {
+ console.error('Error creating RabbitMQ channel:', error);
+ }
+ }
+ return channel;
+}
-/*
-async function publishToQueue(userId, difficulty, language) {
+async function publishToQueue({userId, difficulty, language}) {
try {
- const connection = await amqp.connect(process.env.RABBITMQ_URL);
- const channel = await connection.createChannel();
- const matching_exchange_name = 'matching_exchange';
+ const channel = await connectToRabbitMQ(); // Reuse persistent connection
const routingKey = `${difficulty}.${language}`;
- const queueName = `${difficulty}.${language}`;
- if (queueInfo) {
- channel.publish(matching_exchange_name, routingKey, Buffer.from(JSON.stringify({ userId, language, difficulty })));
+ // Publish the message to the exchange
+ const messageSent = channel.publish(
+ matching_exchange_name,
+ routingKey,
+ Buffer.from(JSON.stringify({ userId, difficulty, language }))
+ );
- console.log(`Published user: ${userId} with routing key: ${routingKey}`);
+ if (messageSent) {
+ console.log(`Message sent: ${userId} -> ${routingKey}`);
} else {
- console.log(`Cannot publish message: Queue ${queueName} does not exist`);
+ console.error(`Message NOT sent: ${userId} -> ${routingKey}`);
}
-
-
- await channel.close();
- await connection.close();
} catch (error) {
console.error('Error publishing to RabbitMQ:', error);
}
}
-module.exports = { publishToQueue };
-*/
-
-
-
-
-
+async function publishCancelRequest({ userId }) {
+ try {
+ const channel = await connectToRabbitMQ(); // Reuse persistent connection
+ const routingKey = 'cancel'; // Define a routing key for cancellation
+
+ // Publish the cancel message to the exchange
+ const messageSent = channel.publish(
+ matching_exchange_name,
+ routingKey,
+ Buffer.from(JSON.stringify({ userId }))
+ );
+
+ if (messageSent) {
+ console.log(`Cancel request sent: ${userId}`);
+ } else {
+ console.error(`Cancel request NOT sent: ${userId}`);
+ }
+ } catch (error) {
+ console.error('Error publishing cancel request to RabbitMQ:', error);
+ }
+}
+module.exports = { publishToQueue, publishCancelRequest };
diff --git a/Backend/MatchingService/rabbitmq/setup.js b/Backend/MatchingService/rabbitmq/setup.js
index 79e17c9651..a2cc7bf202 100644
--- a/Backend/MatchingService/rabbitmq/setup.js
+++ b/Backend/MatchingService/rabbitmq/setup.js
@@ -1,12 +1,24 @@
const amqp = require("amqplib");
+const matching_exchange_name = "matching_exchange";
+const dead_letter_exchange_name = "dead_letter_exchange";
+const dead_letter_queue_name = "dead_letter_queue";
+const cancel_queue_name = "cancel_queue";
+const queueNames = [
+ 'easy.python',
+ 'easy.java',
+ 'easy.cplusplus',
+ 'medium.python',
+ 'medium.java',
+ 'medium.cplusplus',
+ 'hard.python',
+ 'hard.java',
+ 'hard.cplusplus',
+];
+
async function setupRabbitMQ() {
try {
- const connection = await amqp.connect(process.env.RABBITMQ_URL)
- .catch((error) => {
- console.error("Error connecting to RabbitMQ:", error);
- return null;
- });
+ const connection = await amqp.connect(process.env.RABBITMQ_URL);
if (!connection) {
return;
@@ -14,65 +26,46 @@ async function setupRabbitMQ() {
const channel = await connection.createChannel();
- // Declare matching exchange to be bind to queues
- const matching_exchange_name = "matching_exchange";
- await channel.assertExchange(matching_exchange_name, "topic", { durable: false });
+ // Declare the matching exchange (topic)
+ await channel.assertExchange(matching_exchange_name, "topic", { durable: true });
- // Declare dead letter exchange
- const dead_letter_exchange_name = "dead_letter_exchange";
- await channel.assertExchange(dead_letter_exchange_name, "fanout", { durable: false });
+ // Declare the dead-letter exchange (fanout)
+ await channel.assertExchange(dead_letter_exchange_name, "fanout", { durable: true });
- const queueNames = [
- 'easy.python',
- 'easy.java',
- 'easy.cplusplus',
- 'medium.python',
- 'medium.java',
- 'medium.cplusplus',
- 'hard.python',
- 'hard.java',
- 'hard.cplusplus'
- ]
+ // Declare and bind all main queues with TTL and DLQ bindings
+ for (let queueName of queueNames) {
+ await channel.deleteQueue(queueName); // Ensure we start fresh for each setup
- // Create and bind queues to exchange with the routing keys
- for (let name of queueNames) {
- /*
- try {
- await channel.deleteQueue(name);
- } catch (err) {
- console.log(`Queue ${name} does not exist or could not be deleted: ${err.message}`);
- }
- */
- await channel.assertQueue(name,
- { durable: false, // durable=false ensures queue will survive broker restarts
- arguments: {
- 'x-dead-letter-exchange': dead_letter_exchange_name // set dead letter exchange
- }
-
- });
+ await channel.assertQueue(queueName, {
+ durable: true,
+ arguments: {
+ 'x-message-ttl': 10000, // 60 seconds TTL
+ 'x-dead-letter-exchange': dead_letter_exchange_name // Bind to dead-letter exchange
+ }
+ });
- await channel.bindQueue(name, matching_exchange_name, name); // e.g. messages with routing key easy.python goes to easy.python queue
+ await channel.bindQueue(queueName, matching_exchange_name, queueName); // Bind to exchange
}
- // Create and bind queue to exchange (if we want only 1 queue)
- // await channel.assertQueue(name, { durable: false })
- // await channel.bindQueue(name, matching_exchange_name, '#') // all messages go to this queue because of a wildcard pattern
+ // Delete DLQ before asserting it
+ await channel.deleteQueue(dead_letter_queue_name);
- // Create and bind dead letter queue
- // const dead_letter_queue_name = "dead_letter_queue";
- // await channel.assertQueue(deadLetterQueueName, { durable: false });
- // await channel.bindQueue(deadLetterQueueName, deadLetterExchangeName, ''); // Bind all messages to this queue
+ // Declare the dead-letter queue and bind it to the dead-letter exchange
+ await channel.assertQueue(dead_letter_queue_name, { durable: true });
+ await channel.bindQueue(dead_letter_queue_name, dead_letter_exchange_name, ''); // Bind with no routing key
+ // Declare and bind the cancel queue
+ await channel.deleteQueue(cancel_queue_name); // Delete any existing cancel queue
+ await channel.assertQueue(cancel_queue_name, { durable: true }); // Declare the cancel queue
+ await channel.bindQueue(cancel_queue_name, matching_exchange_name, 'cancel'); // Bind with the "cancel" routing key
- console.log("RabbitMQ setup complete with queues and bindings.")
+ console.log("RabbitMQ setup complete with queues, DLQ, and bindings.");
await channel.close();
await connection.close();
} catch (error) {
- console.log('Error setting up RabbitMQ:', error);
+ console.error("Error setting up RabbitMQ:", error);
}
}
-module.exports = { setupRabbitMQ };
-
-setupRabbitMQ()
\ No newline at end of file
+module.exports = { setupRabbitMQ, matching_exchange_name, queueNames, dead_letter_queue_name , cancel_queue_name};
\ No newline at end of file
diff --git a/Backend/MatchingService/rabbitmq/subscriber.js b/Backend/MatchingService/rabbitmq/subscriber.js
index 4191e89142..e77b378809 100644
--- a/Backend/MatchingService/rabbitmq/subscriber.js
+++ b/Backend/MatchingService/rabbitmq/subscriber.js
@@ -1,38 +1,242 @@
const amqp = require('amqplib');
+const { queueNames } = require('./setup.js');
+// const { matchUsers } = require('../services/matchingService.js');
+const { notifyUsers } = require('../websocket/websocket');
// TODO: Subscribe and acknowledge messages with user info when timeout/user matched
-//const { matchUsers } = require('../services/matchingService');
+// To remember what goes in a subscriber use some Acronym
+// Connect, Assert, Process, E - for Acknowledge
+
+const dead_letter_queue_name = "dead_letter_queue";
+const timeoutMap = {};
+
+// Local dictionary to store waiting users
+const waitingUsers = {};
+
+// using promises to handle errors and ensure clearing of timer.
+function matchUsers(channel, msg, userId, language, difficulty) {
+ const criteriaKey = `${difficulty}.${language}`;
+
+ // If the criteria key does not exist, create it
+ if (!waitingUsers[criteriaKey]) {
+ waitingUsers[criteriaKey] = [];
+ }
+
+ // Store both the userId, message, and the channel in waitingUsers
+ waitingUsers[criteriaKey].push({ userId, msg, channel });
+ console.log(`User ${userId} added to ${criteriaKey}. Waiting list: ${waitingUsers[criteriaKey].length}`);
+
+ // Check if there are 2 or more users waiting for this criteria
+ if (waitingUsers[criteriaKey].length >= 2) {
+ const matchedUsers = waitingUsers[criteriaKey].splice(0, 2); // Match the first two users
+ console.log(`Matched users: ${matchedUsers.map(user => user.userId)}`);
+
+ // Notify users of the match
+ notifyUsers(matchedUsers.map(user => user.userId), 'Match found!', 'match');
+
+ // Acknowledge the messages for both matched users
+ matchedUsers.forEach(({ msg, channel }) => {
+ acknowledgeMessage(channel, msg);
+ });
+
+ return true;
+ }
+
+ return false;
+}
+
+
+async function acknowledgeMessage(channel, msg) {
+ return new Promise((resolve, reject) => {
+ try {
+ channel.ack(msg);
+ console.log(`Acknowledged message for user: ${JSON.parse(msg.content).userId}`);
+ clearTimeout(timeoutMap[JSON.parse(msg.content).userId]); // Clear any pending timeout
+ delete timeoutMap[JSON.parse(msg.content).userId]; // Clean up
+ resolve();
+ } catch (error) {
+ console.error(`Failed to acknowledge message:`, error);
+ reject(error);
+ }
+ });
+}
+
+async function rejectMessage(channel, msg, userId) {
+ return new Promise((resolve, reject) => {
+ try {
+ // Get user data from the message to find the correct key in waitingUsers
+ const userData = JSON.parse(msg.content.toString());
+ const { language, difficulty } = userData;
+
+ // Correctly creating the criteriaKey using template literals
+ const criteriaKey = `${difficulty}.${language}`;
+
+
+ // Find the user in the waitingUsers list and remove them
+ if (waitingUsers[criteriaKey]) {
+ // Find the index of the user in the waiting list
+ const userIndex = waitingUsers[criteriaKey].findIndex(user => user.userId === userId);
+
+ if (userIndex !== -1) {
+ // Remove the user from the waiting list
+ waitingUsers[criteriaKey].splice(userIndex, 1);
+ console.log(`Removed user ${userId} from waiting list for ${criteriaKey}`);
+ }
+ }
+
+ // Reject the message without requeuing
+ channel.reject(msg, false); // Reject without requeuing
+ console.log(`Rejected message for user: ${userId}`);
+
+ // Clean up the timeoutMap
+ if (timeoutMap[userId]) {
+ clearTimeout(timeoutMap[userId]);
+ delete timeoutMap[userId];
+ }
+
+ resolve();
+ } catch (error) {
+ console.error(`Failed to reject message for user ${userId}:, error`);
+ reject(error);
+ }
+ });
+}
-/*
async function consumeQueue() {
try {
const connection = await amqp.connect(process.env.RABBITMQ_URL);
const channel = await connection.createChannel();
- const exchange = 'matching_exchange';
- // Consuming messages from multiple queues (already created in setup)
- const queueNames = ['easy.python', 'easy.java', 'medium.python', 'medium.java', 'hard.python', 'hard.java'];
-
- console.log("Waiting for users...")
+ console.log("Waiting for users...");
+ // Process + subscribe to each matchmaking queue
for (let queueName of queueNames) {
- channel.consume(queueName, (msg) => {
+ await channel.consume(queueName, async (msg) => {
if (msg !== null) {
const userData = JSON.parse(msg.content.toString());
- // const { userId, language, difficulty } = userData;
+ const { userId, language, difficulty } = userData;
// Perform the matching logic
- // matchUsers(userId, language, difficulty);
- console.log(userData);
+ console.log(`Received user ${userId} with ${language} and ${difficulty}`);
+
+ // Call matchUsers with channel, message, and user details
+ const matched = matchUsers(channel, msg, userId, language, difficulty);
+
+ if (!matched) {
+ console.log(`No match for ${userId}, waiting for rejection timeout.`);
- channel.ack(msg);
+ const timeoutId = setTimeout(async () => {
+ await rejectMessage(channel, msg, userId);
+ }, 10000); // 10 seconds delay
+
+ timeoutMap[userId] = timeoutId;
+ }
}
- });
+ }, { noAck: false }); // Ensure manual acknowledgment
}
+
+ console.log("Listening to matchmaking queues");
+
+ await consumeCancelQueue();
+ console.log("Listening to Cancel Queue");
} catch (error) {
console.error('Error consuming RabbitMQ queue:', error);
}
}
-*/
+async function consumeDLQ() {
+ try {
+ const connection = await amqp.connect(process.env.RABBITMQ_URL);
+ const channel = await connection.createChannel();
+
+ // Consume messages from the DLQ
+ await channel.consume(dead_letter_queue_name, (msg) => {
+ if (msg !== null) {
+ const messageContent = JSON.parse(msg.content.toString());
+ const { userId, difficulty, language } = messageContent;
+
+ console.log(`Received message from DLQ for user: ${userId}`);
+
+ // Notify the user via WebSocket
+ notifyUsers(userId, `Match not found for ${difficulty} ${language}, please try again.`, 'rejection');
+
+ // Acknowledge the message (so it's removed from the DLQ)
+ channel.ack(msg);
+ }
+ });
+
+ console.log(`Listening to Dead Letter Queue for unmatched users...`);
+ } catch (error) {
+ console.error('Error consuming from DLQ:', error);
+ }
+}
+
+async function consumeCancelQueue() {
+ try {
+ const connection = await amqp.connect(process.env.RABBITMQ_URL);
+ const channel = await connection.createChannel();
+
+ // Subscribe to the cancel queue
+ await channel.consume('cancel_queue', async (msg) => {
+ if (msg !== null) {
+ const { userId } = JSON.parse(msg.content.toString());
+ console.log(`Received cancel request for user: ${userId}`);
+
+ // Process the cancel request
+ await cancelMatching(channel, msg, userId);
+ }
+ }, { noAck: false }); // Ensure manual acknowledgment
+
+ console.log("Listening for cancel requests");
+ } catch (error) {
+ console.error('Error consuming cancel queue:', error);
+ }
+}
+
+async function cancelMatching(cancelChannel, cancelMsg, userId) {
+ try {
+ let foundOriginalMsg = false;
+
+ // Loop through waitingUsers to find the original message for the user
+ Object.keys(waitingUsers).forEach(criteriaKey => {
+ const userIndex = waitingUsers[criteriaKey].findIndex(user => user.userId === userId);
+
+ if (userIndex !== -1) {
+ const { msg, channel } = waitingUsers[criteriaKey][userIndex]; // Get original msg and its channel
+
+ // Acknowledge the original matchmaking message from the queue (e.g., easy.python)
+ if (msg && channel) {
+ console.log(`Acknowledging original message for user ${userId} in queue ${criteriaKey}`);
+ channel.ack(msg); // Use the same channel that consumed the message to acknowledge it
+ foundOriginalMsg = true;
+ }
+
+ // Remove the user from the waiting list
+ waitingUsers[criteriaKey].splice(userIndex, 1);
+ console.log(`User ${userId} removed from waiting list for ${criteriaKey}`);
+ }
+ });
+
+ // If original message not found, log a warning
+ if (!foundOriginalMsg) {
+ console.warn(`Original message for user ${userId} not found in matchmaking queues.`);
+ }
+
+ // Clear any timeouts for the user
+ if (timeoutMap[userId]) {
+ clearTimeout(timeoutMap[userId]);
+ delete timeoutMap[userId];
+ }
+
+ // Acknowledge the cancel message from the cancel queue
+ cancelChannel.ack(cancelMsg);
+ console.log(`Cancel processed for user ${userId}`);
+
+ } catch (error) {
+ console.error(`Failed to process cancel for user ${userId}:`, error);
+ }
+}
+
+
+module.exports = { consumeQueue, consumeDLQ };
\ No newline at end of file
diff --git a/Backend/MatchingService/services/matchingService.js b/Backend/MatchingService/services/matchingService.js
deleted file mode 100644
index a8f431ae50..0000000000
--- a/Backend/MatchingService/services/matchingService.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// TODO: Matching users logic
-
-
-/*
-const { notifyUsers } = require('./websocket/websocket)
-const waitingUsers = {};
-
-function matchUsers(userId, language, difficulty) {
- const criteriaKey = `${difficulty}.${language}`;
-
- if (!waitingUsers[criteriaKey]) {
- waitingUsers[criteriaKey] = [];
- }
-
- waitingUsers[criteriaKey].push(userId);
- console.log(`User ${userId} added to ${criteriaKey}. Waiting list: ${waitingUsers[criteriaKey].length}`);
-
- // Check if there are 2 or more users waiting for this criteria
- if (waitingUsers[criteriaKey].length >= 2) {
- const matchedUsers = waitingUsers[criteriaKey].splice(0, 2); // Match the first two users
- console.log(`Matched users: ${matchedUsers}`);
-
- // Send match success (this could trigger WebSocket communication)
- notifyUsers(matchedUsers);
- return true;
- }
-
- return false;
-}
-
-module.exports = { matchUsers };
-*/
\ No newline at end of file
diff --git a/Backend/MatchingService/websocket/websocket.js b/Backend/MatchingService/websocket/websocket.js
index 5abf34f5e9..89531623e0 100644
--- a/Backend/MatchingService/websocket/websocket.js
+++ b/Backend/MatchingService/websocket/websocket.js
@@ -1,14 +1,42 @@
-// TODO: Write socket logic to connect backend to frontend here
-
-/*
+const { publishToQueue , publishCancelRequest} = require('../rabbitmq/publisher');
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
console.log('User connected to WebSocket');
-
- ws.on('message', (message) => {
- console.log(`Received message: ${message}`);
+
+ // Listen for messages from the frontend
+ ws.on('message', async (message) => {
+ try {
+ console.log(`Received message: ${message}`);
+
+ // Parse the message to extract userId, difficulty, and language
+ const { userId, difficulty, language , action} = JSON.parse(message);
+
+ // Store userId in WebSocket connection
+ ws.userId = userId;
+
+ if (action === 'match') {
+ // Call the RabbitMQ publisher to publish this message to the queue
+ await publishToQueue({ userId, difficulty, language });
+ console.log('Message published to RabbitMQ');
+
+ // Notify the user that their message has been processed successfully
+ ws.send(JSON.stringify({ status: 'su9ccess', message: 'Match request sent!' }));
+ } else if (action === 'cancel') {
+ await publishCancelRequest({ userId });
+ console.log('Cancel request published to RabbitMQ');
+
+ // Notify the user that their cancel request has been processed successfully
+ ws.send(JSON.stringify({ status: 'success', message: 'Match request cancelled!' }));
+ }
+
+
+
+ } catch (error) {
+ console.error('Error handling WebSocket message:', error);
+ ws.send(JSON.stringify({ status: 'error', message: 'Match request failed!' }));
+ }
});
ws.on('close', () => {
@@ -16,13 +44,28 @@ wss.on('connection', (ws) => {
});
});
-function notifyUsers(userId, message) {
+
+/**
+ * Notify users through WebSocket.
+ * @param {string|array} userId - User ID or an array of user IDs to notify.
+ * @param {string} message - The message to send.
+ * @param {string} type - The type of message (e.g., 'match' or 'rejection').
+ */
+function notifyUsers(userId, message, type) {
+ console.log(`Notifying user(s): ${userId}, Message: ${message}, Type: ${type}`);
+
+ const userIds = Array.isArray(userId) ? userId : [userId]; // Convert to array if single user
+
wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN) {
- client.send(JSON.stringify({ userId, message }));
+ if (client.readyState === WebSocket.OPEN && userIds.includes(client.userId)) {
+ console.log(`Notifying client: ${client.userId}`);
+ client.send(JSON.stringify({
+ userId: client.userId,
+ message,
+ type
+ }));
}
});
}
module.exports = { notifyUsers };
-*/
\ No newline at end of file
diff --git a/Backend/user-service/Dockerfile b/Backend/user-service/Dockerfile
index 6d624d200a..a2809796d5 100644
--- a/Backend/user-service/Dockerfile
+++ b/Backend/user-service/Dockerfile
@@ -7,5 +7,5 @@ RUN npm install
COPY . .
-EXPOSE 3002
+EXPOSE 8080
CMD ["npm", "start"]
diff --git a/Frontend/src/components/NavigationBar.jsx b/Frontend/src/components/NavigationBar.jsx
index 2720880c55..82ad0fdafe 100644
--- a/Frontend/src/components/NavigationBar.jsx
+++ b/Frontend/src/components/NavigationBar.jsx
@@ -14,9 +14,14 @@ function NavigationBar() {
return (
+ Language: {capitalizeFirstLetter(language ? language : "Not Selected")}
+
+
{`${minutes < 10 ? `0${minutes}` : minutes}:${seconds < 10 ? `0${seconds}` : seconds}`}
+ +