Skip to content

Commit 51f7b1c

Browse files
authored
Merge pull request #294 from boostcampwm-2024/dev-be
[MERGE] dev-be to dev
2 parents da5f390 + c1e14b4 commit 51f7b1c

23 files changed

+486
-96
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { HttpStatus } from '@nestjs/common';
2+
import { WsException } from '@nestjs/websockets';
3+
4+
class ChatException extends WsException {
5+
statusCode: number;
6+
constructor({ statusCode, message } : ChatError , public roomId?: string) {
7+
super({ statusCode, message, roomId });
8+
this.statusCode = statusCode;
9+
}
10+
11+
getError() {
12+
return {
13+
statusCode: this.statusCode,
14+
msg: this.message,
15+
roomId: this.roomId || null,
16+
};
17+
}
18+
}
19+
20+
interface ChatError {
21+
statusCode: number;
22+
message: string;
23+
}
24+
25+
const CHATTING_SOCKET_ERROR = {
26+
ROOM_EMPTY: {
27+
statusCode: HttpStatus.BAD_REQUEST,
28+
message: '유저가 참여하고 있는 채팅방이 없습니다.'
29+
},
30+
31+
ROOM_EXISTED: {
32+
statusCode: HttpStatus.BAD_REQUEST,
33+
message: '이미 존재하는 방입니다.'
34+
},
35+
36+
INVALID_USER: {
37+
statusCode: HttpStatus.UNAUTHORIZED,
38+
message: '유효하지 않는 유저입니다.'
39+
},
40+
41+
UNAUTHORIZED: {
42+
statusCode: HttpStatus.UNAUTHORIZED,
43+
message: '해당 명령에 대한 권한이 없습니다.'
44+
},
45+
46+
QUESTION_EMPTY: {
47+
statusCode: HttpStatus.BAD_REQUEST,
48+
message: '유효하지 않은 질문입니다.'
49+
},
50+
51+
BAN_USER: {
52+
statusCode: HttpStatus.FORBIDDEN,
53+
message: '호스트에 의해 밴 당한 유저입니다.'
54+
},
55+
56+
MSG_TOO_LONG:{
57+
statusCode: HttpStatus.NOT_ACCEPTABLE,
58+
message: '메세지의 내용이 없거나, 길이가 150자를 초과했습니다.'
59+
}
60+
61+
62+
};
63+
export { CHATTING_SOCKET_ERROR, ChatException };
64+

backend/chatServer/src/chat/chat.gateway.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,35 @@ import {
66
OnGatewayConnection,
77
OnGatewayDisconnect,
88
MessageBody,
9-
ConnectedSocket,
9+
ConnectedSocket
1010
} from '@nestjs/websockets';
1111
import { UseGuards } from '@nestjs/common';
1212
import { Server, Socket } from 'socket.io';
1313
import {
14-
CHATTING_SOCKET_DEFAULT_EVENT,
15-
CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
14+
CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
1615
} from '../event/constants';
1716
import {
17+
BanUserIncomingMessageDto,
1818
NormalIncomingMessageDto, NoticeIncomingMessageDto, QuestionDoneIncomingMessageDto, QuestionIncomingMessageDto
1919
} from '../event/dto/IncomingMessage.dto';
2020
import { JoiningRoomDto } from '../event/dto/JoiningRoom.dto';
2121
import { RoomService } from '../room/room.service';
2222
import { createAdapter } from '@socket.io/redis-adapter';
23-
import { HostGuard, MessageGuard } from './chat.guard';
23+
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
2424
import { LeavingRoomDto } from '../event/dto/LeavingRoom.dto';
2525
import {
2626
NormalOutgoingMessageDto,
2727
NoticeOutgoingMessageDto,
2828
QuestionOutgoingMessageDto
2929
} from '../event/dto/OutgoingMessage.dto';
3030
import { QuestionDto } from '../event/dto/Question.dto';
31+
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';
3132

32-
@WebSocketGateway({ cors: true })
33+
@WebSocketGateway({
34+
cors: true,
35+
pingInterval: 30000,
36+
pingTimeout: 10000,
37+
})
3338
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
3439
constructor(private roomService: RoomService) {};
3540

@@ -46,7 +51,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
4651

4752
async handleConnection(client: Socket) {
4853
console.log(`Client connected: ${client.id}`);
49-
const user = await this.roomService.createUser(client.id);
54+
const user = await this.roomService.createUser(client);
5055
console.log(user);
5156

5257
/*
@@ -69,6 +74,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
6974
}
7075

7176
// 특정 방에 참여하기 위한 메서드
77+
@UseGuards(BlacklistGuard)
7278
@SubscribeMessage(CHATTING_SOCKET_DEFAULT_EVENT.JOIN_ROOM)
7379
async handleJoinRoom(client: Socket, payload: JoiningRoomDto) {
7480
const { roomId, userId } = payload;
@@ -93,17 +99,20 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
9399
}
94100

95101
// 방에 NORMAL 메시지를 보내기 위한 메서드
96-
@UseGuards(MessageGuard)
102+
@UseGuards(MessageGuard, BlacklistGuard)
97103
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NORMAL)
98104
async handleNormalMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NormalIncomingMessageDto) {
99105
const { roomId, userId, msg } = payload;
100106
const user = await this.roomService.getUserByClientId(client.id);
101107
const normalOutgoingMessage: Omit<NormalOutgoingMessageDto, 'owner'> = {
102108
roomId,
103-
...user,
109+
nickname: user.nickname,
110+
color: user.color,
111+
entryTime: user.entryTime,
104112
msg,
105113
msgTime: new Date().toISOString(),
106-
msgType: 'normal'
114+
msgType: 'normal',
115+
socketId: client.id
107116
};
108117
console.log('Normal Message Come In: ', normalOutgoingMessage);
109118
const hostId = await this.roomService.getHostOfRoom(roomId);
@@ -121,18 +130,21 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
121130
}
122131

123132
// 방에 QUESTION 메시지를 보내기 위한 메서드
124-
@UseGuards(MessageGuard)
133+
@UseGuards(MessageGuard,BlacklistGuard)
125134
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.QUESTION)
126135
async handleQuestionMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: QuestionIncomingMessageDto) {
127136
const { roomId, msg } = payload;
128137
const user = await this.roomService.getUserByClientId(client.id);
129138
const questionWithoutId: Omit<QuestionDto, 'questionId'> = {
130139
roomId,
131-
...user,
140+
nickname: user.nickname,
141+
color: user.color,
142+
entryTime: user.entryTime,
132143
msg,
133144
msgTime: new Date().toISOString(),
134145
msgType: 'question',
135-
questionDone: false
146+
questionDone: false,
147+
socketId: client.id
136148
};
137149

138150
const question: QuestionOutgoingMessageDto = await this.roomService.addQuestion(roomId, questionWithoutId);
@@ -150,19 +162,34 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
150162
}
151163

152164
// 방에 NOTICE 메시지를 보내기 위한 메서드
153-
@UseGuards(MessageGuard)
154-
@UseGuards(HostGuard)
165+
@UseGuards(MessageGuard, HostGuard)
155166
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NOTICE)
156167
async handleNoticeMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NoticeIncomingMessageDto) {
157168
const { roomId, msg } = payload;
158169
const user = await this.roomService.getUserByClientId(client.id);
159170
const noticeOutgoingMessage: NoticeOutgoingMessageDto = {
160171
roomId,
161-
...user,
172+
nickname: user.nickname,
173+
color: user.color,
174+
entryTime: user.entryTime,
162175
msg,
163176
msgTime: new Date().toISOString(),
164177
msgType: 'notice'
165178
};
166179
this.server.to(roomId).emit(CHATTING_SOCKET_RECEIVE_EVENT.NOTICE, noticeOutgoingMessage);
167180
}
181+
182+
@UseGuards(HostGuard)
183+
@SubscribeMessage(CHATTING_SOCKET_DEFAULT_EVENT.BAN_USER)
184+
async handleBanUserMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: BanUserIncomingMessageDto) {
185+
const { roomId, socketId } = payload;
186+
const banUser = await this.roomService.getUserByClientId(socketId);
187+
console.log('banUSer = ', banUser);
188+
if(!banUser) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);
189+
const { address, userAgent } = banUser;
190+
if(!userAgent) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);
191+
192+
await this.roomService.addUserToBlacklist(roomId, address, userAgent);
193+
console.log(await this.roomService.getUserBlacklist(roomId, address));
194+
}
168195
}

backend/chatServer/src/chat/chat.guard.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
22
import { RoomService } from '../room/room.service';
3+
import { Socket } from 'socket.io';
4+
5+
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';
36

47
@Injectable()
58
export class MessageGuard implements CanActivate {
69
constructor() {};
710
canActivate(context: ExecutionContext): boolean {
811
const payload = context.switchToWs().getData();
912
const { msg } = payload;
10-
return !!msg && msg.length <= 150;
13+
if(!!msg && msg.length <= 150) return true;
14+
throw new ChatException(CHATTING_SOCKET_ERROR.MSG_TOO_LONG);
1115
}
1216
}
1317

@@ -19,6 +23,35 @@ export class HostGuard implements CanActivate {
1923
const { roomId, userId } = payload;
2024
const hostId = await this.roomService.getHostOfRoom(roomId);
2125
console.log('hostGuard:', hostId, userId);
22-
return hostId === userId;
26+
if (hostId === userId) return true;
27+
throw new ChatException(CHATTING_SOCKET_ERROR.UNAUTHORIZED, roomId);
28+
}
29+
}
30+
31+
@Injectable()
32+
export class BlacklistGuard implements CanActivate {
33+
constructor(private roomService: RoomService) {};
34+
async canActivate(context: ExecutionContext) {
35+
const payload = context.switchToWs().getData();
36+
const { roomId } = payload;
37+
38+
const client: Socket = context.switchToWs().getClient<Socket>();
39+
const address = client.handshake.address.replaceAll('::ffff:', '');
40+
const userAgent = client.handshake.headers['user-agent'];
41+
42+
if(!userAgent) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);
43+
const isValidUser = await this.whenJoinRoom(roomId, address, userAgent);
44+
45+
if(!isValidUser) throw new ChatException(CHATTING_SOCKET_ERROR.BAN_USER, roomId);
46+
return true;
47+
}
48+
49+
async whenJoinRoom(roomId: string, address: string, userAgent: string) {
50+
console.log(roomId, address, userAgent);
51+
const blacklistInRoom = await this.roomService.getUserBlacklist(roomId, address);
52+
console.log(blacklistInRoom);
53+
const isInBlacklistUser = blacklistInRoom.some((bannedUserAgent) => bannedUserAgent === userAgent);
54+
console.log('blacklistInRoom:', isInBlacklistUser);
55+
return !isInBlacklistUser;
2356
}
2457
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Module } from '@nestjs/common';
22
import { ChatGateway } from './chat.gateway';
33
import { RoomModule } from '../room/room.module';
4-
import { MessageGuard } from './chat.guard';
4+
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
55

66
@Module({
77
imports: [RoomModule],
8-
providers: [ChatGateway, MessageGuard],
8+
providers: [ChatGateway, MessageGuard, BlacklistGuard, HostGuard],
99
})
1010
export class ChatModule {}
Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { HttpStatus } from '@nestjs/common';
2-
31
const CHATTING_SOCKET_DEFAULT_EVENT = {
42
JOIN_ROOM: 'join_room',
53
LEAVE_ROOM: 'leave_room',
4+
BAN_USER: 'ban_user',
65
};
76

87
const CHATTING_SOCKET_RECEIVE_EVENT = {
@@ -20,28 +19,4 @@ const CHATTING_SOCKET_SEND_EVENT = {
2019
NOTICE: 'send_notice'
2120
};
2221

23-
const CHATTING_SOCKET_ERROR = {
24-
ROOM_EMPTY : {
25-
statusCode: HttpStatus.BAD_REQUEST,
26-
message: '유저가 참여하고 있는 채팅방이 없습니다.'
27-
},
28-
29-
ROOM_EXISTED: {
30-
statusCode: HttpStatus.BAD_REQUEST,
31-
message: '이미 존재하는 방입니다.'
32-
},
33-
34-
INVALID_USER: {
35-
statusCode: HttpStatus.UNAUTHORIZED,
36-
message: '유효하지 않는 유저입니다.'
37-
},
38-
39-
QUESTION_EMPTY: {
40-
statusCode: HttpStatus.BAD_REQUEST,
41-
message: '유효하지 않은 질문입니다.'
42-
},
43-
44-
45-
};
46-
47-
export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_ERROR};
22+
export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT};

backend/chatServer/src/event/dto/IncomingMessage.dto.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ class NoticeIncomingMessageDto extends DefaultIncomingMessageDto {
2121
msg: string = '';
2222
}
2323

24+
class BanUserIncomingMessageDto extends DefaultIncomingMessageDto {
25+
userId: string = '';
26+
socketId: string = '';
27+
}
28+
2429

2530

2631
export {
2732
NormalIncomingMessageDto,
2833
QuestionIncomingMessageDto,
2934
QuestionDoneIncomingMessageDto,
3035
DefaultIncomingMessageDto,
31-
NoticeIncomingMessageDto
36+
NoticeIncomingMessageDto,
37+
BanUserIncomingMessageDto
3238
};

backend/chatServer/src/event/dto/OutgoingMessage.dto.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,31 @@ class DefaultOutgoingMessageDto {
55
roomId: string = '';
66
nickname: string = '';
77
color: string = '';
8+
entryTime: string = '';
89
msgTime: string = new Date().toISOString();
910
}
1011

1112
class NormalOutgoingMessageDto extends DefaultOutgoingMessageDto {
1213
msg: string = '';
1314
msgType: OutgoingMessageType = 'normal';
1415
owner: WhoAmI = 'user';
16+
socketId: string = '';
1517
}
1618

1719
class QuestionOutgoingMessageDto extends DefaultOutgoingMessageDto {
1820
msg: string = '';
1921
questionId: number = -1;
2022
questionDone: boolean = false;
2123
msgType: OutgoingMessageType = 'question';
24+
socketId: string = '';
2225
}
2326

24-
class QuestionDoneOutgoingMessageDto extends QuestionOutgoingMessageDto {}
27+
class QuestionDoneOutgoingMessageDto extends DefaultOutgoingMessageDto {
28+
msg: string = '';
29+
questionId: number = -1;
30+
questionDone: boolean = false;
31+
msgType: OutgoingMessageType = 'question';
32+
}
2533

2634
class NoticeOutgoingMessageDto extends DefaultOutgoingMessageDto {
2735
msg: string = '';

backend/chatServer/src/event/dto/Question.dto.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ class QuestionDto {
44
roomId: string = '';
55
nickname: string = '';
66
color: string = '';
7+
entryTime: string = '';
78
msg: string = '';
89
msgTime: string = new Date().toISOString();
910
msgType: OutgoingMessageType = 'question';
1011
questionId: number = -1;
12+
socketId: string = '';
1113
questionDone: boolean = false;
1214
}
1315

0 commit comments

Comments
 (0)