Skip to content

Commit ebfd967

Browse files
committed
Add redis message queue
1 parent 249cfcc commit ebfd967

24 files changed

+564
-129
lines changed

backend/docker-compose.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ services:
8686
networks:
8787
- backend-network
8888
ports:
89-
- "3003:4000"
89+
- "3004:4000"
90+
91+
redis:
92+
image: redis:latest
93+
ports:
94+
- "6379:6379"
95+
networks:
96+
- backend-network
9097

9198
networks:
9299
backend-network:

backend/gateway-service/package-lock.json

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/gateway-service/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"@nestjs/websockets": "^10.4.4",
3131
"class-transformer": "^0.5.1",
3232
"class-validator": "^0.14.1",
33+
"ioredis": "^5.4.1",
3334
"passport-jwt": "^4.0.1",
3435
"reflect-metadata": "^0.1.13",
3536
"rxjs": "^7.8.1"

backend/gateway-service/src/app.module.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { AuthController } from './modules/auth/auth.controller';
55
import { QuestionController } from './modules/question/question.controller';
66
import { APP_GUARD } from '@nestjs/core';
77
import { AtAuthGuard, RtAuthGuard } from './common/guards';
8-
import { MatchController } from './modules/match/match.controller';
8+
import { MatchGateway } from './modules/match/match.controller';
9+
import { RedisService } from './modules/match/redis.service';
910

1011
@Module({
1112
imports: [
@@ -36,21 +37,23 @@ import { MatchController } from './modules/match/match.controller';
3637
},
3738
{
3839
name: 'MATCHING_SERVICE',
39-
transport: Transport.REDIS,
40+
transport: Transport.TCP,
4041
options: {
41-
host: 'localhost',
42-
port: 6379,
42+
host: 'matching-service',
43+
port: 3004,
4344
},
4445
}
4546
]),
4647
],
47-
controllers: [UserController, QuestionController, AuthController, MatchController],
48+
controllers: [UserController, QuestionController, AuthController],
4849
providers: [
4950
RtAuthGuard,
5051
{
5152
provide: APP_GUARD,
5253
useClass: AtAuthGuard,
5354
},
55+
MatchGateway,
56+
RedisService,
5457
],
5558
})
5659
export class AppModule {}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { UserMatchOptionsDto } from './user-match-options.dto';
2+
export { MatchRequestDto } from './match-request.dto';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Type } from 'class-transformer';
2+
import { IsArray, IsNotEmpty, IsString } from 'class-validator';
3+
4+
export class MatchRequestDto {
5+
@IsString()
6+
@IsNotEmpty()
7+
userId: string;
8+
9+
@IsArray()
10+
@IsString({ each: true })
11+
@IsNotEmpty()
12+
@Type(() => String)
13+
selectedTopic: string[];
14+
15+
@IsString()
16+
@IsNotEmpty()
17+
selectedDifficulty: string;
18+
}

backend/gateway-service/src/modules/match/dto/user-match-options.dto.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import { Type } from 'class-transformer';
33
import { IsArray, IsIn, IsNotEmpty, IsString } from 'class-validator';
44

55
export class UserMatchOptionsDto {
6+
7+
@IsString()
8+
userId: string;
9+
610
@ApiProperty({
711
description: 'Either Easy, Medium, or Hard',
812
example: 'Easy',
913
})
1014
@IsString()
1115
@IsNotEmpty()
1216
@IsIn(['Easy', 'Medium', 'Hard'])
13-
difficulty: string;
17+
selectedDifficulty: string;
1418

1519
@ApiProperty({
1620
example: ['Stack', 'String'],
@@ -19,5 +23,5 @@ export class UserMatchOptionsDto {
1923
@IsString({ each: true })
2024
@IsNotEmpty()
2125
@Type(() => String)
22-
categories: string[];
26+
selectedTopic: string[];
2327
}
Lines changed: 63 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,81 @@
1-
import { Inject } from '@nestjs/common';
2-
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
3-
import { ClientProxy } from '@nestjs/microservices';
4-
import { Socket } from 'socket.io';
51
import {
2+
WebSocketGateway,
3+
WebSocketServer,
4+
OnGatewayInit,
65
ConnectedSocket,
7-
MessageBody,
86
SubscribeMessage,
9-
WebSocketGateway,
7+
MessageBody,
108
} from '@nestjs/websockets';
9+
import { Server, Socket } from 'socket.io';
10+
import { RedisService } from './redis.service';
11+
import { ClientProxy } from '@nestjs/microservices';
12+
import { Inject } from '@nestjs/common';
1113

12-
@ApiTags('match')
13-
@ApiBearerAuth('access-token')
1414
@WebSocketGateway({ namespace: '/match' })
15-
export class MatchController {
15+
export class MatchGateway implements OnGatewayInit {
16+
@WebSocketServer() server: Server;
17+
1618
constructor(
17-
@Inject('MATCHING_SERVICE') private readonly matchingClient: ClientProxy,
19+
private redisService: RedisService,
20+
@Inject('MATCHING_SERVICE') private matchingClient: ClientProxy,
1821
) {}
1922

23+
afterInit() {
24+
// Subscribe to Redis Pub/Sub for user match notifications
25+
this.redisService.subscribeToUserMatchEvents((payload) => {
26+
this.notifyUsers(payload);
27+
});
28+
}
29+
30+
// Handle user connection and add them to a room based on their userId
31+
handleConnection(@ConnectedSocket() client: Socket) {
32+
const userId = client.handshake.query.userId; // Extract userId from query
33+
if (userId) {
34+
client.join(userId); // Make the client join a room based on userId
35+
console.log(`User ${userId} connected and joined room ${userId}`);
36+
} else {
37+
console.log('User connected without userId');
38+
}
39+
}
40+
2041
@SubscribeMessage('requestMatch')
2142
async handleRequestMatch(
2243
@ConnectedSocket() client: Socket,
2344
@MessageBody() payload: any,
24-
): Promise<void> {
25-
// Send the match request to the microservice
26-
this.matchingClient
27-
.emit('match.request', {
28-
userId: payload.userId,
29-
topic: payload.topic,
30-
difficulty: payload.difficulty,
31-
})
32-
.subscribe({
33-
next: () => {
34-
client.emit('matchInProgress', 'Finding a match...');
35-
},
36-
error: (err) => {
37-
client.emit('matchError', 'Error finding match');
38-
},
39-
});
45+
) {
46+
const matchPayload = {
47+
userId: payload.userId,
48+
selectedTopic: payload.selectedTopic,
49+
selectedDifficulty: payload.selectedDifficulty,
50+
};
51+
52+
// Emit match request event to the microservice
53+
const timeoutRef = setTimeout(() => {
54+
client.emit('matchTimeout', 'No match found within 30 seconds');
55+
}, 30000); // Timeout after 30 seconds if no match is found
56+
57+
this.matchingClient.emit('match.request', matchPayload).subscribe({
58+
next: () => {
59+
// If a match is found, clear the timeout
60+
clearTimeout(timeoutRef);
61+
client.emit('matchInProgress', 'Finding a match...');
62+
},
63+
error: (err) => {
64+
clearTimeout(timeoutRef); // Also clear the timeout in case of an error
65+
client.emit('matchError', 'Error finding match');
66+
},
67+
});
4068
}
4169

42-
@SubscribeMessage('cancelMatch')
43-
async handleCancelMatch(
44-
@ConnectedSocket() client: Socket, // Access Socket.io client
45-
@MessageBody() payload: any, // Payload from the client
46-
): Promise<void> {
47-
// Send the cancel request to the matching microservice
48-
this.matchingClient
49-
.emit('match.cancel', { userId: payload.userId })
50-
.subscribe({
51-
next: () => {
52-
client.emit('matchCancelled', 'Match cancelled.');
53-
},
54-
error: (err) => {
55-
client.emit('cancelError', 'Error cancelling match');
56-
},
70+
// Notify matched users via WebSocket
71+
notifyUsers(userIds: string[]) {
72+
userIds.forEach((userId, index) => {
73+
const matchedUserId = userIds[index === 0 ? 1 : 0]; // Get the other matched user's ID
74+
console.log(`Notifying user ${userId} about match with ${matchedUserId}`);
75+
this.server.to(userId).emit('matchNotification', {
76+
message: `You have been matched with user ${matchedUserId}`,
77+
matchedUserId: matchedUserId,
5778
});
79+
});
5880
}
5981
}

0 commit comments

Comments
 (0)