Skip to content

Commit 6f44ddf

Browse files
committed
Enforce validation on cancelMatch
1 parent b6542c2 commit 6f44ddf

File tree

4 files changed

+118
-73
lines changed

4 files changed

+118
-73
lines changed

backend/gateway-service/src/modules/match/match.controller.ts

Lines changed: 68 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,16 @@ export class MatchGateway implements OnGatewayInit {
6363
!payload.selectedTopic ||
6464
!payload.selectedDifficulty
6565
) {
66-
client.emit(
67-
MATCH_ERROR,
68-
'Invalid match request. Please check your payload and try again.',
69-
);
66+
client.emit(EXCEPTION, 'Invalid match request payload.');
67+
return;
68+
}
69+
70+
// Retrieve the userId associated with the current socket
71+
const storedUserId = [...this.userSockets.entries()].find(
72+
([, socketId]) => socketId === client.id,
73+
)?.[0];
74+
if (!storedUserId || storedUserId !== payload.userId) {
75+
client.emit(EXCEPTION, 'UserId does not match the current socket.');
7076
return;
7177
}
7278

@@ -80,10 +86,10 @@ export class MatchGateway implements OnGatewayInit {
8086
message: result.message,
8187
});
8288
} else {
83-
client.emit(MATCH_ERROR, result.message);
89+
client.emit(EXCEPTION, result.message);
8490
}
8591
} catch (error) {
86-
client.emit(MATCH_ERROR, `Error requesting match: ${error.message}`);
92+
client.emit(EXCEPTION, `Error requesting match: ${error.message}`);
8793
return;
8894
}
8995
}
@@ -95,24 +101,29 @@ export class MatchGateway implements OnGatewayInit {
95101
) {
96102
if (!payload.userId) {
97103
client.emit(
98-
MATCH_ERROR,
104+
EXCEPTION,
99105
'Invalid userId. Please check your payload and try again.',
100106
);
101107
return;
102108
}
103109

104-
firstValueFrom(
105-
this.matchingClient.send('match-cancel', { userId: payload.userId }),
106-
)
107-
.then(() => console.log(`Match canceled for user ${payload.userId}`))
108-
.catch((error) =>
109-
console.error(
110-
`Error canceling match for user ${payload.userId}: ${error.message}`,
111-
),
110+
try {
111+
const result = await firstValueFrom(
112+
this.matchingClient.send('match-cancel', { userId: payload.userId }),
112113
);
113-
this.server.to(client.id).emit(MATCH_CANCELLED, {
114-
message: `You have been cancelled from the match.`,
115-
});
114+
115+
if (result.success) {
116+
this.server.to(client.id).emit(MATCH_CANCELLED, {
117+
message: result.message,
118+
});
119+
} else {
120+
client.emit(EXCEPTION, result.message);
121+
}
122+
} catch (error) {
123+
console.log(error);
124+
client.emit(EXCEPTION, `Error canceling match: ${error.message}`);
125+
return;
126+
}
116127
}
117128

118129
// Notify both matched users via WebSocket
@@ -148,21 +159,17 @@ export class MatchGateway implements OnGatewayInit {
148159
const id = client.handshake.query.userId as string;
149160

150161
if (!id) {
151-
client.emit(
152-
EXCEPTION,
153-
'Error connecting to /match socket: No userId provided.',
154-
);
155-
client.disconnect();
162+
this.emitExceptionAndDisconnect(client, 'Invalid userId.');
156163
return;
157164
}
158165

159166
try {
160167
// Check if user is already connected
161168
const existingSocketId = this.userSockets.get(id);
162169
if (existingSocketId) {
163-
client.emit(
164-
EXCEPTION,
165-
'Error connecting to /match socket: User already connected.',
170+
this.emitExceptionAndDisconnect(
171+
client,
172+
`User ${id} is already connected with socket ID ${existingSocketId}`,
166173
);
167174
return;
168175
}
@@ -173,11 +180,7 @@ export class MatchGateway implements OnGatewayInit {
173180
);
174181

175182
if (!existingUser) {
176-
client.emit(
177-
EXCEPTION,
178-
'Error connecting to /match socket: Invalid userId.',
179-
);
180-
client.disconnect();
183+
this.emitExceptionAndDisconnect(client, `User ${id} not found.`);
181184
return;
182185
}
183186

@@ -186,10 +189,7 @@ export class MatchGateway implements OnGatewayInit {
186189
console.log(`User ${id} connected with socket ID ${client.id}`);
187190
}
188191
} catch (error) {
189-
client.emit(
190-
EXCEPTION,
191-
`Error connecting to /match socket: ${error.message}`,
192-
);
192+
this.emitExceptionAndDisconnect(client, error.message);
193193
return;
194194
}
195195
}
@@ -199,21 +199,44 @@ export class MatchGateway implements OnGatewayInit {
199199
([, socketId]) => socketId === client.id,
200200
)?.[0];
201201

202-
if (userId) {
203-
this.userSockets.delete(userId);
202+
if (!userId) {
203+
this.emitExceptionAndDisconnect(
204+
client,
205+
'User not found in userSockets at disconnect.',
206+
);
207+
return;
208+
}
209+
210+
try {
204211
// Remove user from Redis pool
205-
firstValueFrom(this.matchingClient.send('match-cancel', { userId }))
206-
.then(() => console.log(`Match canceled for user ${userId}`))
207-
.catch((error) =>
208-
console.error(
209-
`Error canceling match for user ${userId}: ${error.message}`,
210-
),
212+
const result = await firstValueFrom(
213+
this.matchingClient.send('match-cancel', { userId }),
214+
);
215+
216+
if (result.success) {
217+
console.log(`Match canceled successfully for user ${userId}`);
218+
} else {
219+
console.warn(
220+
`Match cancellation failed for user ${userId}: ${result.message}`,
211221
);
212-
console.log(`User ${userId} disconnected`);
222+
}
223+
224+
this.userSockets.delete(userId);
225+
console.log(`User ${userId} disconnected and removed from userSockets.`);
226+
} catch (error) {
227+
client.emit(
228+
EXCEPTION,
229+
`Error canceling match for user ${userId}: ${error.message}`,
230+
);
213231
}
214232
}
215233

216-
getUserSocketId(userId: string): string | undefined {
234+
private getUserSocketId(userId: string): string | undefined {
217235
return this.userSockets.get(userId);
218236
}
237+
238+
private emitExceptionAndDisconnect(client: Socket, message: string) {
239+
client.emit(EXCEPTION, `Error connecting to /match socket: ${message}`);
240+
client.disconnect();
241+
}
219242
}

backend/matching-service/src/app.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ export class AppController {
1414

1515
@MessagePattern('match-cancel')
1616
async handleMatchCancel(@Payload() data: { userId: string }) {
17-
await this.appService.cancelMatch(data.userId);
17+
return this.appService.cancelMatch(data.userId);
1818
}
1919
}

backend/matching-service/src/app.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class AppService {
1313
}
1414

1515
// Remove user from the Redis pool
16-
async cancelMatch(userId: string): Promise<void> {
17-
await this.redisService.removeUsersFromPool([userId]);
16+
async cancelMatch(userId: string): Promise<MatchResponse> {
17+
return this.redisService.removeUsersFromPool([userId]);
1818
}
1919
}

backend/matching-service/src/redis.service.ts

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ export class RedisService {
2222
});
2323
}
2424

25-
// Add user to Redis pool
2625
async addUserToPool(data: MatchRequestDto): Promise<MatchResponse> {
2726
try {
2827
const payload: MatchJob = {
@@ -31,8 +30,8 @@ export class RedisService {
3130
selectedDifficulty: data.selectedDifficulty,
3231
timestamp: Date.now(),
3332
};
34-
console.log('Adding user to pool:', payload);
3533

34+
// Checks if the user is already in the pool
3635
const existingUser = await this.getUserFromPool(data.userId);
3736
if (existingUser) {
3837
return {
@@ -54,34 +53,47 @@ export class RedisService {
5453
}
5554
}
5655

57-
async getUserFromPool(userId: string): Promise<boolean> {
58-
const users = await this.redisPublisher.smembers('userPool');
59-
return users.some((user) => JSON.parse(user).userId === userId);
60-
}
61-
62-
// Get users from Redis pool
63-
async getAllUsersFromPool(): Promise<MatchJob[]> {
64-
const users = await this.redisPublisher.smembers('userPool');
65-
return users.map((user) => JSON.parse(user));
66-
}
56+
async removeUsersFromPool(userIds: string[]): Promise<MatchResponse> {
57+
try {
58+
const pipeline = this.redisPublisher.pipeline();
6759

68-
// Remove users from Redis pool
69-
async removeUsersFromPool(userIds: string[]) {
70-
const users = await this.getAllUsersFromPool();
60+
// Check if the pool is empty
61+
const users = await this.getAllUsersFromPool();
62+
if (users.length === 0) {
63+
return {
64+
success: false,
65+
message: 'No users in the pool.',
66+
};
67+
}
7168

72-
// Find and remove users whose userId matches the provided userIds
73-
userIds.forEach(async (userId) => {
74-
const userToRemove = users.find((user) => user.userId === userId);
75-
if (userToRemove) {
76-
await this.redisPublisher.srem(
77-
'userPool',
78-
JSON.stringify(userToRemove),
79-
);
69+
// Find users whose userId matches the provided userIds
70+
const usersToRemove = users.filter((user) =>
71+
userIds.includes(user.userId),
72+
);
73+
if (usersToRemove.length === 0) {
74+
return {
75+
success: false,
76+
message: 'No matching users found in the pool.',
77+
};
8078
}
81-
});
79+
80+
usersToRemove.forEach((user) => {
81+
pipeline.srem('userPool', JSON.stringify(user));
82+
});
83+
await pipeline.exec();
84+
85+
return {
86+
success: true,
87+
message: `Removed ${usersToRemove.length} user(s) from the pool.`,
88+
};
89+
} catch (error) {
90+
return {
91+
success: false,
92+
message: `Failed to remove users from the pool: ${error.message}`,
93+
};
94+
}
8295
}
8396

84-
// Publish matched users to Redis Pub/Sub channel
8597
async publishMatchedUsers(matchedUserIds: string[]): Promise<void> {
8698
await this.redisPublisher.publish(
8799
'matchChannel',
@@ -95,4 +107,14 @@ export class RedisService {
95107
JSON.stringify(timedOutUserIds),
96108
);
97109
}
110+
111+
async getUserFromPool(userId: string): Promise<boolean> {
112+
const user = await this.redisPublisher.hget('userPool', userId);
113+
return user ? JSON.parse(user) : null;
114+
}
115+
116+
async getAllUsersFromPool(): Promise<MatchJob[]> {
117+
const users = await this.redisPublisher.smembers('userPool');
118+
return users.map((user) => JSON.parse(user));
119+
}
98120
}

0 commit comments

Comments
 (0)