Skip to content

Commit 3b32837

Browse files
committed
Merge branch 'main' of github.com:CS3219-AY2425S1/cs3219-ay2425s1-project-g16 into PEER-232-Match-Initiation-UI
Signed-off-by: SeeuSim <[email protected]>
2 parents 1855061 + b6e8d5b commit 3b32837

File tree

18 files changed

+342
-23
lines changed

18 files changed

+342
-23
lines changed

backend/matching/.env.docker

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@ EXPRESS_PORT=9004
55
MATCHING_DB_HOSTNAME=host.docker.internal
66
MATCHING_DB_PORT=6379
77

8+
PEERPREP_USER_HOST=http://host.docker.internal:9001
9+
PEERPREP_QUESTION_HOST=http://host.docker.internal:9002
10+
PEERPREP_COLLAB_HOST=http://host.docker.internal:9003
11+
812
# MATCHING_DB_USERNAME=peerprep-match-express
913
# MATCHING_DB_PASSWORD=G7jBgyz9wGAFQ5La

backend/matching/.env.local

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ EXPRESS_PORT=9004
55
MATCHING_DB_HOSTNAME=localhost
66
MATCHING_DB_PORT=6379
77

8+
9+
PEERPREP_USER_HOST="http://localhost:9001"
10+
PEERPREP_QUESTION_HOST="http://localhost:9002"
11+
PEERPREP_COLLAB_HOST="http://localhost:9003"
12+
13+
14+
815
# MATCHING_DB_USERNAME="peerprep-match-express"
916
# MATCHING_DB_PASSWORD="G7jBgyz9wGAFQ5La"
1017
# REDIS_ARGS="--requirepass G7jBgyz9wGAFQ5La --user ${MATCHING_DB_USERNAME} on >G7jBgyz9wGAFQ5La ~* allcommands --user default off nopass nocommands"

backend/matching/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"description": "",
2020
"dependencies": {
2121
"async": "^3.2.6",
22+
"axios": "^1.7.7",
2223
"cors": "^2.8.5",
2324
"dotenv": "^16.4.5",
2425
"env-cmd": "^10.1.0",

backend/matching/src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export const UI_HOST = process.env.PEERPREP_UI_HOST!;
44

55
export const EXPRESS_PORT = process.env.EXPRESS_PORT;
66

7+
export const PEERPREP_USER_HOST = process.env.PEERPREP_USER_HOST;
8+
export const PEERPREP_QUESTION_HOST = process.env.PEERPREP_QUESTION_HOST;
9+
export const PEERPREP_COLLAB_HOST = process.env.PEERPREP_COLLAB_HOST;
10+
711
const DB_HOSTNAME = process.env.MATCHING_DB_HOSTNAME;
812
const DB_PORT = Number.parseInt(process.env.MATCHING_DB_PORT ?? '6379');
913
// export const DB_URL = `redis://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}`;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import axios from 'axios';
2+
3+
import { PEERPREP_COLLAB_HOST } from '@/config';
4+
5+
export async function createRoom(
6+
userId1: string,
7+
userId2: string,
8+
questionId: string
9+
): Promise<string> {
10+
const response = await axios.get<{ roomName: string }>(`${PEERPREP_COLLAB_HOST}/room`, {
11+
params: {
12+
userid1: userId1,
13+
userid2: userId2,
14+
questionid: questionId,
15+
},
16+
});
17+
18+
if (response.status !== 200 || !response.data?.roomName) {
19+
throw new Error('Failed to create room');
20+
}
21+
22+
return response.data.roomName;
23+
}
Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,47 @@
1-
type IGetMatchItemsParams = {
2-
userId1: string;
3-
userId2: string;
4-
};
5-
6-
export const getMatchItems = ({ userId1, userId2 }: IGetMatchItemsParams) => {
7-
// TODO: Get Match Items
8-
const roomId = `${userId1}${userId2}`;
9-
const questionId = Math.floor(Math.random() * 20) + 1;
10-
11-
return {
12-
roomId,
13-
questionId,
14-
};
15-
};
1+
import { IMatchItemsResponse, IMatchType } from '../types/index';
2+
import { createRoom } from './collab';
3+
import { getRandomQuestion } from './question';
4+
import { fetchAttemptedQuestions } from './user';
5+
6+
export async function getMatchItems(
7+
searchIdentifier: IMatchType,
8+
topic?: string,
9+
difficulty?: string,
10+
userId1?: string,
11+
userId2?: string
12+
): Promise<IMatchItemsResponse | undefined> {
13+
try {
14+
if (!userId1 || !userId2) {
15+
throw new Error('Both user IDs are required');
16+
}
17+
18+
const [attemptedQuestions1, attemptedQuestions2] = await Promise.all([
19+
fetchAttemptedQuestions(userId1),
20+
fetchAttemptedQuestions(userId2),
21+
]);
22+
23+
const allAttemptedQuestions = [...new Set([...attemptedQuestions1, ...attemptedQuestions2])];
24+
25+
const payload = {
26+
attemptedQuestions: allAttemptedQuestions,
27+
...(searchIdentifier === 'difficulty' && difficulty ? { difficulty } : {}),
28+
...(searchIdentifier === 'topic' && topic ? { topic } : {}),
29+
...(searchIdentifier === 'exact match' && topic && difficulty ? { topic, difficulty } : {}),
30+
};
31+
32+
// Get a random question
33+
const question = await getRandomQuestion(payload);
34+
35+
const roomName = await createRoom(userId1, userId2, question.id.toString());
36+
37+
console.log('Successfully got match items');
38+
return {
39+
roomName,
40+
questionId: question.id,
41+
question,
42+
};
43+
} catch (error) {
44+
console.error('Error in getMatchItems:', error);
45+
return undefined;
46+
}
47+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import axios from 'axios';
2+
3+
import { PEERPREP_QUESTION_HOST } from '@/config';
4+
5+
import { IGetRandomQuestionPayload, IQuestion, IServiceResponse } from '../types/index';
6+
7+
export async function getRandomQuestion(payload: IGetRandomQuestionPayload): Promise<IQuestion> {
8+
const response = await axios.post<IServiceResponse<{ question: IQuestion }>>(
9+
`${PEERPREP_QUESTION_HOST}/questions/random`,
10+
payload
11+
);
12+
13+
if (response.status !== 200 || !response.data.data) {
14+
throw new Error(response.data.error?.message || 'Failed to get a random question');
15+
}
16+
17+
return response.data.data.question;
18+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import axios from 'axios';
2+
3+
import { PEERPREP_USER_HOST } from '@/config';
4+
5+
export async function fetchAttemptedQuestions(userId: string): Promise<number[]> {
6+
const response = await axios.post<number[]>(`${PEERPREP_USER_HOST}/user/attempted-question/get`, {
7+
userId,
8+
});
9+
10+
if (response.status !== 200 || !response.data) {
11+
throw new Error(`Failed to fetch attempted questions for user ${userId}`);
12+
}
13+
14+
return response.data || [];
15+
}
16+
17+
export async function updateAttemptedQuestions(
18+
userIds: string[],
19+
questionId: number
20+
): Promise<void> {
21+
const response = await axios.post<unknown>(`${PEERPREP_USER_HOST}/user/attempted-question/add`, {
22+
questionId,
23+
userIds,
24+
});
25+
26+
if (response.status !== 200 || !response.data) {
27+
throw new Error(`Failed to update attempted questions for users ${userIds}`);
28+
}
29+
30+
return;
31+
}

backend/matching/src/types/index.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,30 @@ export type IChildProcessMessage = {
4444
event: IMatchEvent;
4545
message?: unknown;
4646
};
47+
export type IMatchType = 'difficulty' | 'topic' | 'exact match' | undefined;
48+
49+
export interface IServiceResponse<T> {
50+
success: boolean;
51+
data?: T;
52+
error?: { message: string };
53+
}
54+
55+
export interface IQuestion {
56+
id: number;
57+
title: string;
58+
description: string;
59+
difficulty: string;
60+
topic: string[];
61+
}
62+
63+
export interface IGetRandomQuestionPayload {
64+
attemptedQuestions: number[];
65+
difficulty?: string;
66+
topic?: string;
67+
}
68+
69+
export interface IMatchItemsResponse {
70+
roomName: string;
71+
questionId: number;
72+
question: IQuestion;
73+
}

backend/matching/src/workers/matcher.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { client, logQueueStatus } from '@/lib/db';
22
import { POOL_INDEX, STREAM_GROUP, STREAM_NAME, STREAM_WORKER } from '@/lib/db/constants';
33
import { decodePoolTicket, getPoolKey, getStreamId } from '@/lib/utils';
44
import { getMatchItems } from '@/services';
5+
import { IMatchType } from '@/types';
56
import { MATCHING_EVENT } from '@/ws/events';
67

78
import { connectClient, sendNotif } from './common';
@@ -41,7 +42,9 @@ async function processMatch(
4142
redisClient: typeof client,
4243
{ requestorUserId, requestorStreamId, requestorSocketPort }: RequestorParams,
4344
matches: Awaited<ReturnType<(typeof client)['ft']['search']>>,
44-
searchIdentifier?: string
45+
searchIdentifier?: IMatchType,
46+
topic?: string,
47+
difficulty?: string
4548
) {
4649
if (matches.total > 0) {
4750
for (const matched of matches.documents) {
@@ -70,10 +73,17 @@ async function processMatch(
7073
]);
7174

7275
// Notify both sockets
73-
const { ...matchItems } = getMatchItems({ userId1: requestorUserId, userId2: matchedUserId });
76+
const { ...matchItems } = await getMatchItems(
77+
searchIdentifier,
78+
topic,
79+
difficulty,
80+
requestorUserId,
81+
matchedUserId
82+
);
83+
logger.info(`Generated Match - ${JSON.stringify(matchItems)}`);
7484

75-
sendNotif([matchedSocketPort, requestorSocketPort], MATCHING_EVENT.SUCCESS, matchItems);
76-
sendNotif([matchedSocketPort, requestorSocketPort], MATCHING_EVENT.DISCONNECT);
85+
sendNotif([requestorSocketPort, matchedSocketPort], MATCHING_EVENT.SUCCESS, matchItems);
86+
sendNotif([requestorSocketPort, matchedSocketPort], MATCHING_EVENT.DISCONNECT);
7787

7888
await logQueueStatus(logger, redisClient, `Queue Status After Matching: <PLACEHOLDER>`);
7989
return true;
@@ -86,6 +96,7 @@ async function processMatch(
8696

8797
async function match() {
8898
const redisClient = await connectClient(client);
99+
89100
const stream = await redisClient.xReadGroup(
90101
STREAM_GROUP,
91102
STREAM_WORKER,
@@ -143,7 +154,9 @@ async function match() {
143154
redisClient,
144155
requestorParams,
145156
exactMatches,
146-
'exact match'
157+
'exact match',
158+
topic,
159+
difficulty
147160
);
148161

149162
if (exactMatchFound || !topic || !difficulty) {
@@ -161,7 +174,9 @@ async function match() {
161174
redisClient,
162175
requestorParams,
163176
topicMatches,
164-
'topic'
177+
'topic',
178+
topic,
179+
difficulty
165180
);
166181

167182
if (topicMatchFound) {
@@ -178,7 +193,9 @@ async function match() {
178193
redisClient,
179194
requestorParams,
180195
difficultyMatches,
181-
'difficulty'
196+
'difficulty',
197+
topic,
198+
difficulty
182199
);
183200

184201
if (!hasDifficultyMatch) {

0 commit comments

Comments
 (0)