Skip to content

Commit 459479f

Browse files
committed
Implement userid validation
1 parent 01cd1d6 commit 459479f

File tree

9 files changed

+143
-48
lines changed

9 files changed

+143
-48
lines changed

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

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
MATCH_TIMEOUT,
2020
MATCH_REQUESTED,
2121
MATCH_ERROR,
22+
EXCEPTION,
2223
} from './match.event';
2324
import { CANCEL_MATCH, FIND_MATCH } from './match.message';
2425

@@ -37,6 +38,7 @@ export class MatchGateway implements OnGatewayInit {
3738

3839
constructor(
3940
@Inject('MATCHING_SERVICE') private matchingClient: ClientProxy,
41+
@Inject('USER_SERVICE') private userClient: ClientProxy,
4042
private redisService: RedisService,
4143
) {}
4244

@@ -67,22 +69,19 @@ export class MatchGateway implements OnGatewayInit {
6769
);
6870
return;
6971
}
70-
// Send the match request to the microservice
72+
7173
try {
72-
firstValueFrom(this.matchingClient.send('match-request', payload))
73-
.then(() =>
74-
console.log(
75-
`Match request from user ${payload.userId} received successfully`,
76-
),
77-
)
78-
.catch((error) =>
79-
console.error(
80-
`Error requesting match for user ${payload.userId}: ${error.message}`,
81-
),
82-
);
83-
this.server.to(client.id).emit(MATCH_REQUESTED, {
84-
message: `Match request sent to the matching service.`,
85-
});
74+
const result = await firstValueFrom(
75+
this.matchingClient.send('match-request', payload),
76+
);
77+
78+
if (result.success) {
79+
this.server.to(client.id).emit(MATCH_REQUESTED, {
80+
message: result.message,
81+
});
82+
} else {
83+
client.emit(MATCH_ERROR, result.message);
84+
}
8685
} catch (error) {
8786
client.emit(MATCH_ERROR, `Error requesting match: ${error.message}`);
8887
return;
@@ -94,6 +93,14 @@ export class MatchGateway implements OnGatewayInit {
9493
@ConnectedSocket() client: Socket,
9594
@MessageBody() payload: { userId: string },
9695
) {
96+
if (!payload.userId) {
97+
client.emit(
98+
MATCH_ERROR,
99+
'Invalid userId. Please check your payload and try again.',
100+
);
101+
return;
102+
}
103+
97104
firstValueFrom(
98105
this.matchingClient.send('match-cancel', { userId: payload.userId }),
99106
)
@@ -137,11 +144,42 @@ export class MatchGateway implements OnGatewayInit {
137144
});
138145
}
139146

140-
handleConnection(@ConnectedSocket() client: Socket) {
141-
const userId = client.handshake.query.userId;
142-
if (userId) {
143-
this.userSockets.set(userId as string, client.id);
144-
console.log(`User ${userId} connected with socket ID ${client.id}`);
147+
async handleConnection(@ConnectedSocket() client: Socket) {
148+
const id = client.handshake.query.userId;
149+
150+
if (!id) {
151+
client.emit(
152+
EXCEPTION,
153+
'Error connecting to /match socket: No userId provided.',
154+
);
155+
client.disconnect();
156+
return;
157+
}
158+
159+
try {
160+
const existingUser = await firstValueFrom(
161+
this.userClient.send({ cmd: 'get-user-by-id' }, id),
162+
);
163+
164+
if (!existingUser) {
165+
client.emit(
166+
EXCEPTION,
167+
'Error connecting to /match socket: Invalid userId.',
168+
);
169+
client.disconnect();
170+
return;
171+
}
172+
173+
if (id) {
174+
this.userSockets.set(id as string, client.id);
175+
console.log(`User ${id} connected with socket ID ${client.id}`);
176+
}
177+
} catch (error) {
178+
client.emit(
179+
EXCEPTION,
180+
`Error connecting to /match socket: ${error.message}`,
181+
);
182+
return;
145183
}
146184
}
147185

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const MATCH_CANCELLED = 'matchCancelled'; // Emitted when a match is canc
44
export const MATCH_CONFIRMED = 'matchConfirmed'; // Emitted when a match is confirmed
55
export const MATCH_TIMEOUT = 'matchTimeout'; // Emitted after 5 minutes of inactivity
66
export const MATCH_ERROR = 'matchError'; // Emitted when an error occurs
7+
export const EXCEPTION = 'exception'; // Emitted when an exception occurs

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export class AppController {
99

1010
@MessagePattern('match-request')
1111
async handleMatchRequest(@Payload() data: MatchRequestDto) {
12-
await this.appService.requestMatch(data);
12+
return this.appService.requestMatch(data);
1313
}
1414

1515
@MessagePattern('match-cancel')

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import { Injectable } from '@nestjs/common';
22
import { MatchRequestDto } from './dto/match-request.dto';
33
import { RedisService } from './redis.service';
4+
import { MatchResponse } from './interfaces';
45

56
@Injectable()
67
export class AppService {
78
constructor(private redisService: RedisService) {}
89

910
// Add user to the Redis pool
10-
async requestMatch(data: MatchRequestDto): Promise<void> {
11-
await this.redisService.addUserToPool(data);
11+
async requestMatch(data: MatchRequestDto): Promise<MatchResponse> {
12+
return this.redisService.addUserToPool(data);
1213
}
1314

1415
// Remove user from the Redis pool
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './match-job.interface';
2+
export * from './match-response.interface';
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface MatchResponse {
2+
success: boolean;
3+
message: string;
4+
}

backend/matching-service/src/match-worker.service.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,47 +10,68 @@ export class MatchWorkerService {
1010
// Poll for matches at a regular interval
1111
async pollForMatches() {
1212
setInterval(async () => {
13-
const users = await this.redisService.getUsersFromPool();
13+
const users = await this.redisService.getAllUsersFromPool();
1414
const currentTime = Date.now();
1515
const timeout = 300000; // 5 minutes to remove any zombie users
1616
console.log('Polling', users);
1717
// Filter out users who have timed out
18-
const activeUsers = users.filter(user => currentTime - user.timestamp < timeout);
18+
const activeUsers = users.filter(
19+
(user) => currentTime - user.timestamp < timeout,
20+
);
1921

2022
if (activeUsers.length < users.length) {
21-
const timedOutUsers = users.filter(user => currentTime - user.timestamp >= timeout);
22-
await this.notifyGatewayTimeout(timedOutUsers.map(user => user.userId));
23-
await this.redisService.removeUsersFromPool(timedOutUsers.map(user => user.userId));
23+
const timedOutUsers = users.filter(
24+
(user) => currentTime - user.timestamp >= timeout,
25+
);
26+
await this.notifyGatewayTimeout(
27+
timedOutUsers.map((user) => user.userId),
28+
);
29+
await this.redisService.removeUsersFromPool(
30+
timedOutUsers.map((user) => user.userId),
31+
);
2432
}
2533

2634
if (activeUsers.length >= 2) {
2735
const matches = this.rankUsers(activeUsers);
2836
const bestMatch = matches[0];
2937

3038
// Notify the gateway via Redis Pub/Sub
31-
await this.notifyGateway([bestMatch.user1.userId, bestMatch.user2.userId]);
39+
await this.notifyGateway([
40+
bestMatch.user1.userId,
41+
bestMatch.user2.userId,
42+
]);
3243

3344
// Remove the matched users from the Redis pool
34-
await this.redisService.removeUsersFromPool([bestMatch.user1.userId, bestMatch.user2.userId]);
45+
await this.redisService.removeUsersFromPool([
46+
bestMatch.user1.userId,
47+
bestMatch.user2.userId,
48+
]);
3549
}
3650
}, 5000);
3751
}
3852

3953
// Ranking logic for matches
40-
rankUsers(users: MatchRequestDto[]): { user1: MatchRequestDto, user2: MatchRequestDto, score: number }[] {
54+
rankUsers(
55+
users: MatchRequestDto[],
56+
): { user1: MatchRequestDto; user2: MatchRequestDto; score: number }[] {
4157
const matches = [];
4258
for (let i = 0; i < users.length; i++) {
4359
for (let j = i + 1; j < users.length; j++) {
4460
const score = this.calculateScore(users[i], users[j]);
4561
matches.push({ user1: users[i], user2: users[j], score });
4662
}
4763
}
48-
return matches.sort((a, b) => b.score - a.score)
64+
return matches.sort((a, b) => b.score - a.score);
4965
}
5066

51-
private calculateScore(user1: MatchRequestDto, user2: MatchRequestDto): number {
67+
private calculateScore(
68+
user1: MatchRequestDto,
69+
user2: MatchRequestDto,
70+
): number {
5271
let score = 0;
53-
const matchingTopics = user1.selectedTopic.filter(topic => user2.selectedTopic.includes(topic));
72+
const matchingTopics = user1.selectedTopic.filter((topic) =>
73+
user2.selectedTopic.includes(topic),
74+
);
5475
score += matchingTopics.length * 3;
5576
if (user1.selectedDifficulty === user2.selectedDifficulty) {
5677
score += 2;
@@ -67,4 +88,3 @@ export class MatchWorkerService {
6788
await this.redisService.publishTimedOutUsers(userIds);
6889
}
6990
}
70-

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

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import Redis from 'ioredis';
33
import { MatchRequestDto } from './dto/match-request.dto';
44
import { MatchJob } from './interfaces/match-job.interface';
55
import { config } from 'src/configs';
6+
import { RpcException } from '@nestjs/microservices';
7+
import { MatchResponse } from './interfaces';
68

79
@Injectable()
810
export class RedisService {
@@ -21,27 +23,51 @@ export class RedisService {
2123
}
2224

2325
// Add user to Redis pool
24-
async addUserToPool(data: MatchRequestDto): Promise<void> {
25-
const payload: MatchJob = {
26-
userId: data.userId,
27-
selectedTopic: data.selectedTopic,
28-
selectedDifficulty: data.selectedDifficulty,
29-
timestamp: Date.now(),
30-
};
26+
async addUserToPool(data: MatchRequestDto): Promise<MatchResponse> {
27+
try {
28+
const payload: MatchJob = {
29+
userId: data.userId,
30+
selectedTopic: data.selectedTopic,
31+
selectedDifficulty: data.selectedDifficulty,
32+
timestamp: Date.now(),
33+
};
34+
console.log('Adding user to pool:', payload);
3135

32-
33-
await this.redisPublisher.sadd('userPool', JSON.stringify(payload));
36+
const existingUser = await this.getUserFromPool(data.userId);
37+
if (existingUser) {
38+
return {
39+
success: false,
40+
message: 'User already in pool',
41+
};
42+
}
43+
44+
await this.redisPublisher.sadd('userPool', JSON.stringify(payload));
45+
return {
46+
success: true,
47+
message: 'User added to pool successfully',
48+
};
49+
} catch (error) {
50+
return {
51+
success: false,
52+
message: `Failed to add user to matching pool: ${error.message}`,
53+
};
54+
}
55+
}
56+
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);
3460
}
3561

3662
// Get users from Redis pool
37-
async getUsersFromPool(): Promise<MatchJob[]> {
63+
async getAllUsersFromPool(): Promise<MatchJob[]> {
3864
const users = await this.redisPublisher.smembers('userPool');
3965
return users.map((user) => JSON.parse(user));
4066
}
4167

4268
// Remove users from Redis pool
4369
async removeUsersFromPool(userIds: string[]) {
44-
const users = await this.getUsersFromPool();
70+
const users = await this.getAllUsersFromPool();
4571

4672
// Find and remove users whose userId matches the provided userIds
4773
userIds.forEach(async (userId) => {

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import * as bcrypt from 'bcryptjs';
21
import { Injectable } from '@nestjs/common';
32
import { InjectModel } from '@nestjs/mongoose';
4-
import { Model } from 'mongoose';
3+
import mongoose, { Model } from 'mongoose';
54
import { RpcException } from '@nestjs/microservices';
65
import { User } from './schema/user.schema';
76
import {
@@ -25,6 +24,10 @@ export class AppService {
2524
}
2625

2726
public async getUserById(id: string): Promise<User> {
27+
if (!mongoose.Types.ObjectId.isValid(id)) {
28+
throw new RpcException('Invalid _id format (Provide an ObjectId)');
29+
}
30+
2831
const user = await this.userModel.findById(id).exec();
2932
return user;
3033
}

0 commit comments

Comments
 (0)