Skip to content

Commit 81a87f8

Browse files
committed
Refactor
1 parent 0d744bf commit 81a87f8

File tree

12 files changed

+171
-208
lines changed

12 files changed

+171
-208
lines changed

backend/matching-service/package-lock.json

Lines changed: 1 addition & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/matching-service/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@
1919
"dotenv": "^16.4.5",
2020
"express": "^4.21.1",
2121
"socket.io": "^4.8.0",
22-
"swagger-ui-express": "^5.0.1",
23-
"uuid": "^10.0.0",
24-
"yaml": "^2.5.1"
22+
"uuid": "^10.0.0"
2523
},
2624
"devDependencies": {
2725
"@eslint/js": "^9.12.0",

backend/matching-service/src/app.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
11
import express, { Request, Response } from "express";
22
import dotenv from "dotenv";
3-
import swaggerUi from "swagger-ui-express";
4-
import yaml from "yaml";
5-
import fs from "fs";
63
import cors from "cors";
74

8-
import matchingRoutes from "./routes/matchingRoutes.ts";
9-
105
dotenv.config();
116

127
export const allowedOrigins = process.env.ORIGINS
138
? process.env.ORIGINS.split(",")
149
: ["http://localhost:5173", "http://127.0.0.1:5173"];
1510

16-
const file = fs.readFileSync("./swagger.yml", "utf-8");
17-
const swaggerDocument = yaml.parse(file);
18-
1911
const app = express();
2012

2113
app.use(cors({ origin: allowedOrigins, credentials: true }));
2214
app.options("*", cors({ origin: allowedOrigins, credentials: true }));
2315

24-
app.use("/api/matching", matchingRoutes);
25-
app.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
2616
app.get("/", (req: Request, res: Response) => {
2717
res.status(200).json({ message: "Hello world from matching service" });
2818
});
Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,113 @@
11
import amqplib, { Connection } from "amqplib";
22
import dotenv from "dotenv";
3-
import { matchUsers } from "../utils/messageQueue";
4-
import { MatchRequestItem } from "../handlers/matchHandler";
3+
import { matchUsers } from "../handlers/matchHandler";
54
import { Complexities, Categories, Languages } from "../utils/constants";
5+
import { MatchRequest, MatchRequestItem } from "../utils/types";
66

77
dotenv.config();
88

99
const RABBITMQ_ADDR = process.env.RABBITMQ_ADDR || "amqp://localhost:5672";
10+
const QUEUE_NAME_DELIMITER = "_";
1011

1112
let mrConnection: Connection;
12-
const queues: string[] = [];
13-
const pendingQueueRequests = new Map<string, Map<string, MatchRequestItem>>();
13+
const waitingLists = new Map<string, Map<string, MatchRequestItem>>();
1414

15-
const initQueueNames = () => {
16-
for (const complexity of Object.values(Complexities)) {
17-
for (const category of Object.values(Categories)) {
18-
for (const language of Object.values(Languages)) {
19-
queues.push(`${complexity}_${category}_${language}`);
20-
}
15+
export const connectToRabbitMq = async () => {
16+
try {
17+
mrConnection = await amqplib.connect(RABBITMQ_ADDR);
18+
const queues = setUpQueueNames();
19+
for (const queue of queues) {
20+
await setUpConsumer(queue);
21+
getWaitingList(queue);
2122
}
23+
} catch (error) {
24+
console.error(error);
25+
process.exit(1);
2226
}
2327
};
2428

25-
const setUpQueue = async (queueName: string) => {
29+
export const sendToProducer = async (
30+
matchRequest: MatchRequest,
31+
requestId: string,
32+
rejectedPartnerId?: string
33+
): Promise<boolean> => {
34+
const { user, complexity, category, language, timeout } = matchRequest;
35+
36+
const requestItem: MatchRequestItem = {
37+
id: requestId,
38+
user: user,
39+
sentTimestamp: Date.now(),
40+
ttlInSecs: timeout,
41+
rejectedPartnerId: rejectedPartnerId,
42+
};
43+
44+
const sent = await routeToQueue(
45+
[complexity, category, language],
46+
requestItem
47+
);
48+
return sent;
49+
};
50+
51+
const setUpConsumer = async (queueName: string) => {
2652
const consumerChannel = await mrConnection.createChannel();
27-
await consumerChannel.assertQueue(queueName);
53+
await consumerChannel.assertQueue(queueName, { durable: true });
2854

2955
consumerChannel.consume(queueName, (msg) => {
3056
if (msg !== null) {
31-
matchUsers(queueName, msg.content.toString());
57+
const matchRequestItem = JSON.parse(msg.content.toString());
58+
const waitingList = getWaitingList(queueName);
59+
const [complexity, category] = deconstructQueueName(queueName);
60+
matchUsers(matchRequestItem, waitingList, complexity, category);
3261
consumerChannel.ack(msg);
3362
}
3463
});
3564
};
3665

37-
export const connectToRabbitMq = async () => {
38-
try {
39-
initQueueNames();
40-
mrConnection = await amqplib.connect(RABBITMQ_ADDR);
41-
for (const queue of queues) {
42-
await setUpQueue(queue);
43-
pendingQueueRequests.set(queue, new Map<string, MatchRequestItem>());
44-
}
45-
} catch (error) {
46-
console.error(error);
47-
process.exit(1);
48-
}
49-
};
50-
51-
export const sendToQueue = async (
52-
complexity: string,
53-
category: string,
54-
language: string,
55-
data: MatchRequestItem
66+
const routeToQueue = async (
67+
criterias: string[],
68+
requestItem: MatchRequestItem
5669
): Promise<boolean> => {
5770
try {
58-
const queueName = `${complexity}_${category}_${language}`;
71+
const queueName = constructQueueName(criterias);
5972
const senderChannel = await mrConnection.createChannel();
60-
senderChannel.sendToQueue(queueName, Buffer.from(JSON.stringify(data)));
73+
senderChannel.sendToQueue(
74+
queueName,
75+
Buffer.from(JSON.stringify(requestItem)),
76+
{
77+
persistent: true,
78+
}
79+
);
6180
return true;
6281
} catch (error) {
6382
console.log(error);
6483
return false;
6584
}
6685
};
6786

68-
export const getPendingRequests = (
69-
queueName: string
70-
): Map<string, MatchRequestItem> => {
71-
return pendingQueueRequests.get(queueName)!;
87+
const setUpQueueNames = () => {
88+
const queues = [];
89+
for (const complexity of Object.values(Complexities)) {
90+
for (const category of Object.values(Categories)) {
91+
for (const language of Object.values(Languages)) {
92+
const queueName = constructQueueName([complexity, category, language]);
93+
queues.push(queueName);
94+
}
95+
}
96+
}
97+
return queues;
98+
};
99+
100+
const constructQueueName = (criterias: string[]) => {
101+
return criterias.join(QUEUE_NAME_DELIMITER);
102+
};
103+
104+
const deconstructQueueName = (queueName: string) => {
105+
return queueName.split(QUEUE_NAME_DELIMITER);
106+
};
107+
108+
const getWaitingList = (queueName: string): Map<string, MatchRequestItem> => {
109+
if (!waitingLists.has(queueName)) {
110+
waitingLists.set(queueName, new Map<string, MatchRequestItem>());
111+
}
112+
return waitingLists.get(queueName)!;
72113
};
Lines changed: 64 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,60 @@
11
import { v4 as uuidv4 } from "uuid";
2-
import { sendToQueue } from "../config/rabbitmq";
3-
import { sendMatchFound } from "./websocketHandler";
2+
import {
3+
isActiveRequest,
4+
isUserConnected,
5+
sendMatchFound,
6+
} from "./websocketHandler";
7+
import { MatchRequestItem, MatchUser } from "../utils/types";
48

59
interface Match {
610
matchUser1: MatchUser;
711
matchUser2: MatchUser;
812
accepted: boolean;
913
complexity: string;
1014
category: string;
11-
language: string;
12-
}
13-
14-
export interface MatchUser {
15-
id: string;
16-
username: string;
17-
profile?: string;
18-
}
19-
20-
export interface MatchRequest {
21-
user: MatchUser;
22-
complexity: string;
23-
category: string;
24-
language: string;
25-
timeout: number;
26-
}
27-
28-
export interface MatchRequestItem {
29-
id: string;
30-
user: MatchUser;
31-
sentTimestamp: number;
32-
ttlInSecs: number;
33-
rejectedPartnerId?: string;
3415
}
3516

3617
const matches = new Map<string, Match>();
3718

38-
export const sendMatchRequest = async (
39-
matchRequest: MatchRequest,
40-
requestId: string,
41-
rejectedPartnerId?: string
42-
): Promise<boolean> => {
43-
const { user, complexity, category, language, timeout } = matchRequest;
44-
45-
const matchItem: MatchRequestItem = {
46-
id: requestId,
47-
user: user,
48-
sentTimestamp: Date.now(),
49-
ttlInSecs: timeout,
50-
rejectedPartnerId: rejectedPartnerId,
51-
};
52-
53-
const sent = await sendToQueue(complexity, category, language, matchItem);
54-
return sent;
55-
};
56-
57-
export const createMatch = (
58-
requestItem1: MatchRequestItem,
59-
requestItem2: MatchRequestItem,
19+
export const matchUsers = (
20+
newRequest: MatchRequestItem,
21+
waitingList: Map<string, MatchRequestItem>,
6022
complexity: string,
61-
category: string,
62-
language: string
23+
category: string
6324
) => {
64-
const matchId = uuidv4();
65-
const matchUser1 = requestItem1.user;
66-
const matchUser2 = requestItem2.user;
25+
const newRequestUid = newRequest.user.id;
26+
27+
for (const [uid, waitListRequest] of waitingList) {
28+
if (
29+
isExpired(waitListRequest) ||
30+
!isUserConnected(uid) ||
31+
!isActiveRequest(uid, waitListRequest.id) ||
32+
uid === newRequestUid
33+
) {
34+
waitingList.delete(uid);
35+
continue;
36+
}
6737

68-
matches.set(matchId, {
69-
matchUser1: matchUser1,
70-
matchUser2: matchUser2,
71-
accepted: false,
72-
complexity,
73-
category,
74-
language,
75-
});
38+
if (
39+
isExpired(newRequest) ||
40+
!isUserConnected(newRequestUid) ||
41+
!isActiveRequest(newRequestUid, newRequest.id)
42+
) {
43+
return;
44+
}
7645

77-
sendMatchFound(matchId, matchUser1, matchUser2);
46+
if (
47+
uid === newRequest.rejectedPartnerId ||
48+
newRequestUid === waitListRequest.rejectedPartnerId
49+
) {
50+
continue;
51+
}
52+
53+
waitingList.delete(uid);
54+
createMatch(waitListRequest.user, newRequest.user, complexity, category);
55+
return;
56+
}
57+
waitingList.set(newRequestUid, newRequest);
7858
};
7959

8060
export const handleMatchAccept = (matchId: string): boolean => {
@@ -116,3 +96,26 @@ export const getMatchByUid = (
11696
export const getMatchById = (matchId: string): Match | undefined => {
11797
return matches.get(matchId);
11898
};
99+
100+
const createMatch = (
101+
matchUser1: MatchUser,
102+
matchUser2: MatchUser,
103+
complexity: string,
104+
category: string
105+
) => {
106+
const matchId = uuidv4();
107+
108+
matches.set(matchId, {
109+
matchUser1: matchUser1,
110+
matchUser2: matchUser2,
111+
accepted: false,
112+
complexity,
113+
category,
114+
});
115+
116+
sendMatchFound(matchId, matchUser1, matchUser2);
117+
};
118+
119+
const isExpired = (data: MatchRequestItem): boolean => {
120+
return Date.now() - data.sentTimestamp >= data.ttlInSecs * 1000;
121+
};

0 commit comments

Comments
 (0)