Skip to content

Commit 0edbb20

Browse files
authored
Merge pull request #33 from CS3219-AY2324S1/assignment5
Provide matching service to PeerPrep. Provide backend support for matching service in Assignment 5.
2 parents 745d6f6 + b2bec95 commit 0edbb20

File tree

16 files changed

+1633
-0
lines changed

16 files changed

+1633
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.find-match-button {
2+
display: flex;
3+
margin: auto;
4+
justify-content: center;
5+
align-items: center;
6+
width: 200px;
7+
height: 50px;
8+
color: #fff;
9+
background: #52CC65;
10+
border: none;
11+
border-radius: 5px;
12+
font-size: 19px;
13+
white-space: nowrap;
14+
cursor: pointer;
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { useState } from 'react';
2+
import MatchPopup from './MatchPopup';
3+
import './MatchButton.css';
4+
5+
export const MatchButton = ({}) => {
6+
const [chosenDifficulty, setChosenDifficulty] = useState('easy');
7+
const [viewPopup, setViewPopup] = useState(false);
8+
9+
function hidePopup() {
10+
return setViewPopup(false);
11+
}
12+
13+
function openPopup() {
14+
return setViewPopup(true)
15+
}
16+
17+
function handleChosenDifficulty(event) {
18+
return setChosenDifficulty(event.target.value)
19+
}
20+
return(
21+
<div className="match-button-container">
22+
<button onClick = {openPopup} className="find-match-button">
23+
Find your Match
24+
</button>
25+
26+
<MatchPopup
27+
isOpen={viewPopup}
28+
isClose={hidePopup}
29+
chosenDifficulty={chosenDifficulty}
30+
onChosenDifficulty={handleChosenDifficulty}
31+
></MatchPopup>
32+
</div>
33+
);
34+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.match-popup {
2+
text-align: center;
3+
background-color: #fff;
4+
border-radius: 7px;
5+
position: relative;
6+
padding: 18px;
7+
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
8+
z-index: 1001;
9+
font-size: 19px;
10+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
11+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
12+
sans-serif;
13+
}
14+
15+
.match-overlay {
16+
display: flex;
17+
justify-content: center;
18+
align-items: center;
19+
position: fixed;
20+
left: 0;
21+
top: 0;
22+
width: 100%;
23+
height: 100%;
24+
background-color: rgba(0, 0, 0, 0.4);
25+
z-index: 1000;
26+
}
27+
28+
.dropdown-menu select {
29+
text-align: center;
30+
font-size: 19px;
31+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
32+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
33+
sans-serif;
34+
}
35+
36+
.hide-popup {
37+
position: absolute;
38+
right: 20px;
39+
top: 10px;
40+
cursor: pointer;
41+
}
42+
43+
.submit-match {
44+
display: flex;
45+
margin: auto;
46+
justify-content: center;
47+
align-items: center;
48+
width: 180px;
49+
height: 45px;
50+
color: #fff;
51+
background: #52CC65;
52+
border: none;
53+
border-radius: 5px;
54+
font-size: 19px;
55+
white-space: nowrap;
56+
margin-top: 20px;
57+
cursor: pointer;
58+
}
59+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import './MatchPopup.css';
3+
import { useEffect, useState } from 'react';
4+
5+
const MatchPopup = ({ isOpen, isClose, chosenDifficulty, onChosenDifficulty, onSubmission}) => {
6+
if (isOpen) {
7+
return (
8+
<div className="match-overlay">
9+
<div className="match-popup">
10+
<span className="hide-popup" onClick={isClose}>&times;</span>
11+
12+
<h3>Finding Your Match</h3>
13+
<div className="dropdown-menu">
14+
<label>Difficulty Level of Questions: </label>
15+
<select value={chosenDifficulty} onChange={onChosenDifficulty}>
16+
<option value="easy">Easy</option>
17+
<option value="medium">Medium</option>
18+
<option value="hard">Hard</option>
19+
</select>
20+
</div>
21+
22+
<div >
23+
<button onClick={onSubmission} className="submit-match">Match</button>
24+
</div>
25+
</div>
26+
</div>
27+
);
28+
}
29+
}
30+
31+
export default MatchPopup;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
npm_debug.log
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM node:18
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY package*.json ./
6+
7+
RUN npm install
8+
9+
COPY . .
10+
11+
EXPOSE 8080
12+
13+
CMD [ "node", "server.js" ]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Matchmaking Microservice for PeerPrep
2+
3+
### To run this microservice, plaese ensure that you have locally installed:
4+
- Node.js ^18.0.0
5+
- RabbitMQ Server ^3.12.5
6+
- MongoDB ^7.0.0
7+
8+
### Run RabbitMQ Server by:
9+
1. Navigating to the Program Files directory
10+
2. ```
11+
cd "..\RabbitMQ Server\rabbitmq_server-3.12.5\sbin"
12+
rabbitmq-service start
13+
rabbitmq-plugins enable rabbitmq_management
14+
```
15+
3. A UI management site will be hosted on `http://localhost:15672/`.
16+
17+
Start the microservice by (In peerprep directory):
18+
```
19+
cd peer-prep\src\backend\matching-service
20+
npm install
21+
npm start
22+
```
23+
24+
### For backend testing:
25+
1. Finding a match:
26+
27+
Send **POST** request to:
28+
29+
`http://localhost:3000/home/:UserId` and specify post fields.
30+
31+
Example:
32+
33+
`http://localhost:3000/home/1`
34+
35+
{ id: 1,
36+
language: "Java",
37+
proficiency: "Advanced",
38+
difficulty: "Easy",
39+
topic: "Strings" }
40+
41+
2. Canceling a match:
42+
43+
Send **DELETE** request to:
44+
45+
`http://localhost:3000/home/:UserId/matching`
46+
47+
Example:
48+
49+
`http://localhost:3000/home/1/matching`
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
mongodbUri: 'mongodb://127.0.0.1:27017/peer-prep',
3+
rabbitmqUrl: 'amqp://127.0.0.1:5672'
4+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const matchingService = require('../services/matchingService');
2+
3+
async function findMatch(req, res, next) {
4+
try {
5+
const { id, language, proficiency, difficulty, topic } = req.body;
6+
console.log(req.body);
7+
console.log(`${id}, ${language}, ${proficiency}, ${difficulty}, ${topic}`);
8+
9+
const matchResult = await matchingService.findMatch({ id, language, proficiency, difficulty, topic });
10+
11+
res.status(200).json(matchResult);
12+
13+
} catch (error) {
14+
next(error);
15+
}
16+
}
17+
18+
async function cancelMatch(req, res, next) {
19+
try {
20+
const isCancelled = await matchingService.cancelMatch(req.params.userId);
21+
if (isCancelled) {
22+
res.status(200).json({ message: 'Match cancelled successfully' });
23+
} else {
24+
throw new Error('Failed to cancel match');
25+
}
26+
} catch (error) {
27+
next(error);
28+
}
29+
}
30+
31+
module.exports = {
32+
findMatch,
33+
cancelMatch
34+
};
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const MatchedPair = require('../models/matchedPairModel');
2+
3+
// Some database utility function for the MatchedPair Schema
4+
async function getMatchedPairBySessionId(sessionId) {
5+
try {
6+
const matchedPair = await MatchedPair.findOne({ sessionId: sessionId });
7+
console.log(`Get matched pair with session id ${sessionId}:`, matchedPair);
8+
return matchedPair;
9+
10+
} catch (error) {
11+
console.error(`Error getting matched pair with session id ${sessionId}:`, error);
12+
}
13+
}
14+
15+
async function getCurrentMatchedPair(id) {
16+
try {
17+
const matchedPair = await MatchedPair.findOne({ $and: [{ isEnded: false }, { $or: [{ id1: id }, { id2: id }] }] });
18+
console.log(`Get live matched pair for ${id}:`, matchedPair);
19+
return matchedPair;
20+
21+
} catch (error) {
22+
console.error(`Error getting live matched pair for ${id}:`, error);
23+
return null;
24+
}
25+
}
26+
27+
async function addMatchedPair(matchedPair) {
28+
try {
29+
await matchedPair.save();
30+
console.log(`Successfully added:`, matchedPair);
31+
32+
} catch (error) {
33+
console.error(`Failed to add matched pair ${matchedPair}:`, error);
34+
}
35+
}
36+
37+
async function endSession(sessionId) {
38+
try {
39+
const filter = { sessionId: sessionId };
40+
const update = { $set: { isEnded: true } };
41+
await MatchedPair.updateOne(filter, update);
42+
console.log(`Successfully update session state for session ${sessionId}`);
43+
44+
} catch (error) {
45+
console.error(`Failed to update session state for session ${sessionId}:`, error);
46+
}
47+
}
48+
49+
async function modifyMatchedPair(sessionId, key, value) {
50+
try {
51+
const filter = { sessionId: sessionId };
52+
const update = {
53+
$set: {
54+
[key]: value
55+
}
56+
};
57+
await MatchedPair.updateOne(filter, update);
58+
console.log(`Successfully update ${key} state for session ${sessionId}: ${value}`);
59+
60+
} catch (error) {
61+
console.error(`Failed to update ${key} state for session ${sessionId}:`, error);
62+
}
63+
}
64+
65+
async function deleteMatchedPair(sessionId) {
66+
try {
67+
await MatchedPair.deleteOne({ sessionId: sessionId });
68+
console.log(`Successfully delete session ${sessionId} from database.`);
69+
70+
} catch (error) {
71+
console.error(`Failed to delete session ${sessionId}:`, error);
72+
}
73+
}
74+
75+
module.exports = {
76+
getMatchedPairBySessionId,
77+
getCurrentMatchedPair,
78+
addMatchedPair,
79+
endSession,
80+
modifyMatchedPair,
81+
deleteMatchedPair
82+
};

0 commit comments

Comments
 (0)