Skip to content

Commit 3fcfda8

Browse files
authored
Merge pull request #62 from CS3219-AY2425S1/PEER-223-Matching-Cancellation-Endpoint
PEER-223: Match Cancellation
2 parents 45a7bc2 + 80e97c4 commit 3fcfda8

File tree

23 files changed

+287
-104
lines changed

23 files changed

+287
-104
lines changed

backend/matching/.env.compose

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
# To be injected by Docker Compose
22
# PEERPREP_UI_HOST="http://frontend:3000"
3+
# MATCHING_DB_HOSTNAME="match-db"
4+
# MATCHING_DB_PORT=6379
35

46
EXPRESS_PORT=9004
57

6-
MATCHING_DB_HOSTNAME="match-db"
7-
MATCHING_DB_PORT=6378
8-
98
MATCHING_DB_USERNAME="peerprep-match-express"
109
MATCHING_DB_PASSWORD="G7jBgyz9wGAFQ5La"
1110
REDIS_ARGS="--requirepass G7jBgyz9wGAFQ5La --user ${MATCHING_DB_USERNAME} on >G7jBgyz9wGAFQ5La ~* allcommands --user default off nopass nocommands"
12-

backend/matching/.env.docker

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ PEERPREP_UI_HOST=http://host.docker.internal:5173
22

33
EXPRESS_PORT=9004
44

5-
MATCHING_DB_HOSTNAME=host.docker.internal
6-
MATCHING_DB_PORT=6379
7-
85
PEERPREP_USER_HOST=http://host.docker.internal:9001
96
PEERPREP_QUESTION_HOST=http://host.docker.internal:9002
107
PEERPREP_COLLAB_HOST=http://host.docker.internal:9003
118

9+
MATCHING_DB_HOSTNAME=host.docker.internal
10+
MATCHING_DB_PORT=6378
11+
1212
MATCHING_DB_USERNAME=peerprep-match-express
1313
MATCHING_DB_PASSWORD=G7jBgyz9wGAFQ5La

backend/matching/.env.local

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ PEERPREP_UI_HOST=http://localhost:5173
22

33
EXPRESS_PORT=9004
44

5+
PEERPREP_USER_HOST=http://localhost:9001
6+
PEERPREP_QUESTION_HOST=http://localhost:9002
7+
PEERPREP_COLLAB_HOST=http://localhost:9003
8+
59
MATCHING_DB_HOSTNAME=localhost
610
MATCHING_DB_PORT=6378
711

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-
1512
MATCHING_DB_USERNAME="peerprep-match-express"
1613
MATCHING_DB_PASSWORD="G7jBgyz9wGAFQ5La"
1714
REDIS_ARGS="--requirepass G7jBgyz9wGAFQ5La --user ${MATCHING_DB_USERNAME} on >G7jBgyz9wGAFQ5La ~* allcommands --user default off nopass nocommands"

backend/matching/src/config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ export const PEERPREP_USER_HOST = process.env.PEERPREP_USER_HOST;
88
export const PEERPREP_QUESTION_HOST = process.env.PEERPREP_QUESTION_HOST;
99
export const PEERPREP_COLLAB_HOST = process.env.PEERPREP_COLLAB_HOST;
1010

11-
const DB_HOSTNAME = process.env.MATCHING_DB_HOSTNAME;
12-
const DB_PORT = Number.parseInt(process.env.MATCHING_DB_PORT ?? '6378');
13-
const DB_USERNAME = process.env.MATCHING_DB_USERNAME;
14-
const DB_PASSWORD = process.env.MATCHING_DB_PASSWORD;
15-
export const DB_URL = `redis://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOSTNAME}:${DB_PORT}`;
16-
// export const DB_URL = `redis://${DB_HOSTNAME}:${DB_PORT}`;
11+
export const DB_HOSTNAME = process.env.MATCHING_DB_HOSTNAME;
12+
export const DB_PORT = Number.parseInt(process.env.MATCHING_DB_PORT ?? '6379');
13+
export const DB_USERNAME = process.env.MATCHING_DB_USERNAME;
14+
export const DB_PASSWORD = process.env.MATCHING_DB_PASSWORD;
1715

1816
export const NODE_ENV = process.env.NODE_ENV;
1917

2018
export const IS_MILESTONE_D4 = true;
19+
20+
export const WORKER_SLEEP_TIME_IN_MILLIS = 500;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { Request, Response } from 'express';
2+
import { StatusCodes } from 'http-status-codes';
3+
4+
import { client as redisClient, logQueueStatus } from '@/lib/db';
5+
import { STREAM_NAME } from '@/lib/db/constants';
6+
import { getPoolKey, getStreamId, logger } from '@/lib/utils';
7+
import { io } from '@/server';
8+
import { MATCHING_EVENT } from '@/ws/events';
9+
10+
export const cancelMatchRequestController = async (req: Request, res: Response) => {
11+
const { userId } = req.body; // Only check for userId
12+
13+
if (!userId) {
14+
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({ message: 'User ID is required' }); // No need for roomId
15+
}
16+
17+
try {
18+
if (!redisClient.isOpen) {
19+
await redisClient.connect();
20+
}
21+
22+
// Check pending status using only userId
23+
const result = await redisClient
24+
.hGetAll(getPoolKey(userId))
25+
.then(async (value) => {
26+
if (value.pending === 'true') {
27+
const timestamp = value.timestamp;
28+
await Promise.all([
29+
redisClient.del(getPoolKey(userId)),
30+
timestamp
31+
? redisClient.xDel(STREAM_NAME, getStreamId(value.timestamp))
32+
: Promise.resolve(),
33+
]);
34+
await logQueueStatus(
35+
logger,
36+
redisClient,
37+
'Queue Status after cancelling request: <PLACEHOLDER>'
38+
);
39+
logger.info(`Request cancellation successful`);
40+
const room = value.socketPort;
41+
42+
if (room) {
43+
io.sockets.in(room).socketsLeave(room);
44+
}
45+
46+
return {
47+
success: true,
48+
};
49+
}
50+
51+
return {
52+
success: false,
53+
error: `Match in ${MATCHING_EVENT.MATCHING} state.`,
54+
};
55+
})
56+
.catch((reason) => {
57+
if (reason) {
58+
return {
59+
success: false,
60+
error: reason,
61+
};
62+
}
63+
});
64+
65+
if (result?.success) {
66+
return res.status(StatusCodes.OK).end();
67+
} else if (!result?.success) {
68+
return res.status(StatusCodes.FORBIDDEN).json(result?.error);
69+
}
70+
} catch (error) {
71+
console.error('Error canceling match:', error);
72+
return res.status(500).json({ message: 'Server error, please try again later' });
73+
}
74+
};

backend/matching/src/lib/db/client.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import { createClient } from 'redis';
22

3-
import { DB_URL } from '@/config';
3+
import { DB_HOSTNAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from '@/config';
44
import { logger } from '@/lib/utils';
55

66
class RedisClient {
77
client: ReturnType<typeof createClient>;
88
constructor() {
99
this.client = createClient({
10-
url: DB_URL,
10+
username: DB_USERNAME,
11+
password: DB_PASSWORD,
12+
socket: {
13+
host: DB_HOSTNAME,
14+
port: DB_PORT,
15+
},
1116
}).on('error', (err) => {
1217
logger.error(`Redis Client error: ${err}`);
1318
});

backend/matching/src/lib/db/log-queue-status.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { STREAM_NAME } from './constants';
55

66
export const getQueueStatusLog = async (redisClient: typeof client) => {
77
const queueStatus = await redisClient.xRange(STREAM_NAME, '-', '+');
8-
const messages = queueStatus.map((v) => v.message);
8+
const messages = queueStatus
9+
.map((v) => v.message)
10+
.map(({ userId, topic, difficulty }) => ({ userId, topic, difficulty }));
911
return JSON.stringify(messages);
1012
};
1113

backend/matching/src/lib/db/seed.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const main = async () => {
3131
const { timeStamp, value } = isSeeded;
3232

3333
if (value === 'true') {
34-
logger.info('Seeded at: ' + new Date(timeStamp).toLocaleString());
34+
logger.info('Seeded at: ' + new Date(Number.parseInt(timeStamp)).toLocaleString());
3535
return;
3636
}
3737
}
@@ -49,6 +49,9 @@ const main = async () => {
4949
difficulty: {
5050
type: SchemaFieldTypes.TAG,
5151
},
52+
pending: {
53+
type: SchemaFieldTypes.TEXT,
54+
},
5255
timestamp: {
5356
type: SchemaFieldTypes.NUMERIC,
5457
SORTABLE: true,

backend/matching/src/lib/utils/get-redis-payload.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export const getRedisPayload = (payload: IPoolTicket) => {
1010
? { topic: topic.join(',') }
1111
: { topic }
1212
: {};
13-
return { ...rest, ...topicField, ...difficultyField };
13+
return { ...rest, ...topicField, ...difficultyField, pending: 'true' };
1414
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { Router } from 'express';
22

3+
import { cancelMatchRequestController } from '@/controllers/cancel-request';
34
import { matchRequestController } from '@/controllers/match-request';
45

56
const route = Router();
67

78
route.post('/request', matchRequestController);
9+
route.post('/cancel', cancelMatchRequestController);
810

911
export default route;

0 commit comments

Comments
 (0)