Skip to content

Commit

Permalink
Merge pull request #294 from boostcampwm-2024/dev-be
Browse files Browse the repository at this point in the history
[MERGE] dev-be to dev
  • Loading branch information
hoeeeeeh authored Dec 30, 2024
2 parents da5f390 + c1e14b4 commit 51f7b1c
Show file tree
Hide file tree
Showing 23 changed files with 486 additions and 96 deletions.
64 changes: 64 additions & 0 deletions backend/chatServer/src/chat/chat.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { HttpStatus } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';

class ChatException extends WsException {
statusCode: number;
constructor({ statusCode, message } : ChatError , public roomId?: string) {
super({ statusCode, message, roomId });
this.statusCode = statusCode;
}

getError() {
return {
statusCode: this.statusCode,
msg: this.message,
roomId: this.roomId || null,
};
}
}

interface ChatError {
statusCode: number;
message: string;
}

const CHATTING_SOCKET_ERROR = {
ROOM_EMPTY: {
statusCode: HttpStatus.BAD_REQUEST,
message: '유저가 참여하고 있는 채팅방이 없습니다.'
},

ROOM_EXISTED: {
statusCode: HttpStatus.BAD_REQUEST,
message: '이미 존재하는 방입니다.'
},

INVALID_USER: {
statusCode: HttpStatus.UNAUTHORIZED,
message: '유효하지 않는 유저입니다.'
},

UNAUTHORIZED: {
statusCode: HttpStatus.UNAUTHORIZED,
message: '해당 명령에 대한 권한이 없습니다.'
},

QUESTION_EMPTY: {
statusCode: HttpStatus.BAD_REQUEST,
message: '유효하지 않은 질문입니다.'
},

BAN_USER: {
statusCode: HttpStatus.FORBIDDEN,
message: '호스트에 의해 밴 당한 유저입니다.'
},

MSG_TOO_LONG:{
statusCode: HttpStatus.NOT_ACCEPTABLE,
message: '메세지의 내용이 없거나, 길이가 150자를 초과했습니다.'
}


};
export { CHATTING_SOCKET_ERROR, ChatException };

57 changes: 42 additions & 15 deletions backend/chatServer/src/chat/chat.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,35 @@ import {
OnGatewayConnection,
OnGatewayDisconnect,
MessageBody,
ConnectedSocket,
ConnectedSocket
} from '@nestjs/websockets';
import { UseGuards } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
import {
CHATTING_SOCKET_DEFAULT_EVENT,
CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_SEND_EVENT
} from '../event/constants';
import {
BanUserIncomingMessageDto,
NormalIncomingMessageDto, NoticeIncomingMessageDto, QuestionDoneIncomingMessageDto, QuestionIncomingMessageDto
} from '../event/dto/IncomingMessage.dto';
import { JoiningRoomDto } from '../event/dto/JoiningRoom.dto';
import { RoomService } from '../room/room.service';
import { createAdapter } from '@socket.io/redis-adapter';
import { HostGuard, MessageGuard } from './chat.guard';
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';
import { LeavingRoomDto } from '../event/dto/LeavingRoom.dto';
import {
NormalOutgoingMessageDto,
NoticeOutgoingMessageDto,
QuestionOutgoingMessageDto
} from '../event/dto/OutgoingMessage.dto';
import { QuestionDto } from '../event/dto/Question.dto';
import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';

@WebSocketGateway({ cors: true })
@WebSocketGateway({
cors: true,
pingInterval: 30000,
pingTimeout: 10000,
})
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
constructor(private roomService: RoomService) {};

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

async handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
const user = await this.roomService.createUser(client.id);
const user = await this.roomService.createUser(client);
console.log(user);

/*
Expand All @@ -69,6 +74,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
}

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

// 방에 NORMAL 메시지를 보내기 위한 메서드
@UseGuards(MessageGuard)
@UseGuards(MessageGuard, BlacklistGuard)
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NORMAL)
async handleNormalMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NormalIncomingMessageDto) {
const { roomId, userId, msg } = payload;
const user = await this.roomService.getUserByClientId(client.id);
const normalOutgoingMessage: Omit<NormalOutgoingMessageDto, 'owner'> = {
roomId,
...user,
nickname: user.nickname,
color: user.color,
entryTime: user.entryTime,
msg,
msgTime: new Date().toISOString(),
msgType: 'normal'
msgType: 'normal',
socketId: client.id
};
console.log('Normal Message Come In: ', normalOutgoingMessage);
const hostId = await this.roomService.getHostOfRoom(roomId);
Expand All @@ -121,18 +130,21 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
}

// 방에 QUESTION 메시지를 보내기 위한 메서드
@UseGuards(MessageGuard)
@UseGuards(MessageGuard,BlacklistGuard)
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.QUESTION)
async handleQuestionMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: QuestionIncomingMessageDto) {
const { roomId, msg } = payload;
const user = await this.roomService.getUserByClientId(client.id);
const questionWithoutId: Omit<QuestionDto, 'questionId'> = {
roomId,
...user,
nickname: user.nickname,
color: user.color,
entryTime: user.entryTime,
msg,
msgTime: new Date().toISOString(),
msgType: 'question',
questionDone: false
questionDone: false,
socketId: client.id
};

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

// 방에 NOTICE 메시지를 보내기 위한 메서드
@UseGuards(MessageGuard)
@UseGuards(HostGuard)
@UseGuards(MessageGuard, HostGuard)
@SubscribeMessage(CHATTING_SOCKET_SEND_EVENT.NOTICE)
async handleNoticeMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: NoticeIncomingMessageDto) {
const { roomId, msg } = payload;
const user = await this.roomService.getUserByClientId(client.id);
const noticeOutgoingMessage: NoticeOutgoingMessageDto = {
roomId,
...user,
nickname: user.nickname,
color: user.color,
entryTime: user.entryTime,
msg,
msgTime: new Date().toISOString(),
msgType: 'notice'
};
this.server.to(roomId).emit(CHATTING_SOCKET_RECEIVE_EVENT.NOTICE, noticeOutgoingMessage);
}

@UseGuards(HostGuard)
@SubscribeMessage(CHATTING_SOCKET_DEFAULT_EVENT.BAN_USER)
async handleBanUserMessage(@ConnectedSocket() client: Socket, @MessageBody() payload: BanUserIncomingMessageDto) {
const { roomId, socketId } = payload;
const banUser = await this.roomService.getUserByClientId(socketId);
console.log('banUSer = ', banUser);
if(!banUser) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);
const { address, userAgent } = banUser;
if(!userAgent) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);

await this.roomService.addUserToBlacklist(roomId, address, userAgent);
console.log(await this.roomService.getUserBlacklist(roomId, address));
}
}
37 changes: 35 additions & 2 deletions backend/chatServer/src/chat/chat.guard.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { RoomService } from '../room/room.service';
import { Socket } from 'socket.io';

import { ChatException, CHATTING_SOCKET_ERROR } from './chat.error';

@Injectable()
export class MessageGuard implements CanActivate {
constructor() {};
canActivate(context: ExecutionContext): boolean {
const payload = context.switchToWs().getData();
const { msg } = payload;
return !!msg && msg.length <= 150;
if(!!msg && msg.length <= 150) return true;
throw new ChatException(CHATTING_SOCKET_ERROR.MSG_TOO_LONG);
}
}

Expand All @@ -19,6 +23,35 @@ export class HostGuard implements CanActivate {
const { roomId, userId } = payload;
const hostId = await this.roomService.getHostOfRoom(roomId);
console.log('hostGuard:', hostId, userId);
return hostId === userId;
if (hostId === userId) return true;
throw new ChatException(CHATTING_SOCKET_ERROR.UNAUTHORIZED, roomId);
}
}

@Injectable()
export class BlacklistGuard implements CanActivate {
constructor(private roomService: RoomService) {};
async canActivate(context: ExecutionContext) {
const payload = context.switchToWs().getData();
const { roomId } = payload;

const client: Socket = context.switchToWs().getClient<Socket>();
const address = client.handshake.address.replaceAll('::ffff:', '');
const userAgent = client.handshake.headers['user-agent'];

if(!userAgent) throw new ChatException(CHATTING_SOCKET_ERROR.INVALID_USER, roomId);
const isValidUser = await this.whenJoinRoom(roomId, address, userAgent);

if(!isValidUser) throw new ChatException(CHATTING_SOCKET_ERROR.BAN_USER, roomId);
return true;
}

async whenJoinRoom(roomId: string, address: string, userAgent: string) {
console.log(roomId, address, userAgent);
const blacklistInRoom = await this.roomService.getUserBlacklist(roomId, address);
console.log(blacklistInRoom);
const isInBlacklistUser = blacklistInRoom.some((bannedUserAgent) => bannedUserAgent === userAgent);
console.log('blacklistInRoom:', isInBlacklistUser);
return !isInBlacklistUser;
}
}
4 changes: 2 additions & 2 deletions backend/chatServer/src/chat/chat.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';
import { RoomModule } from '../room/room.module';
import { MessageGuard } from './chat.guard';
import { BlacklistGuard, HostGuard, MessageGuard } from './chat.guard';

@Module({
imports: [RoomModule],
providers: [ChatGateway, MessageGuard],
providers: [ChatGateway, MessageGuard, BlacklistGuard, HostGuard],
})
export class ChatModule {}
29 changes: 2 additions & 27 deletions backend/chatServer/src/event/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { HttpStatus } from '@nestjs/common';

const CHATTING_SOCKET_DEFAULT_EVENT = {
JOIN_ROOM: 'join_room',
LEAVE_ROOM: 'leave_room',
BAN_USER: 'ban_user',
};

const CHATTING_SOCKET_RECEIVE_EVENT = {
Expand All @@ -20,28 +19,4 @@ const CHATTING_SOCKET_SEND_EVENT = {
NOTICE: 'send_notice'
};

const CHATTING_SOCKET_ERROR = {
ROOM_EMPTY : {
statusCode: HttpStatus.BAD_REQUEST,
message: '유저가 참여하고 있는 채팅방이 없습니다.'
},

ROOM_EXISTED: {
statusCode: HttpStatus.BAD_REQUEST,
message: '이미 존재하는 방입니다.'
},

INVALID_USER: {
statusCode: HttpStatus.UNAUTHORIZED,
message: '유효하지 않는 유저입니다.'
},

QUESTION_EMPTY: {
statusCode: HttpStatus.BAD_REQUEST,
message: '유효하지 않은 질문입니다.'
},


};

export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT, CHATTING_SOCKET_ERROR};
export { CHATTING_SOCKET_DEFAULT_EVENT, CHATTING_SOCKET_SEND_EVENT, CHATTING_SOCKET_RECEIVE_EVENT};
8 changes: 7 additions & 1 deletion backend/chatServer/src/event/dto/IncomingMessage.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ class NoticeIncomingMessageDto extends DefaultIncomingMessageDto {
msg: string = '';
}

class BanUserIncomingMessageDto extends DefaultIncomingMessageDto {
userId: string = '';
socketId: string = '';
}



export {
NormalIncomingMessageDto,
QuestionIncomingMessageDto,
QuestionDoneIncomingMessageDto,
DefaultIncomingMessageDto,
NoticeIncomingMessageDto
NoticeIncomingMessageDto,
BanUserIncomingMessageDto
};
10 changes: 9 additions & 1 deletion backend/chatServer/src/event/dto/OutgoingMessage.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,31 @@ class DefaultOutgoingMessageDto {
roomId: string = '';
nickname: string = '';
color: string = '';
entryTime: string = '';
msgTime: string = new Date().toISOString();
}

class NormalOutgoingMessageDto extends DefaultOutgoingMessageDto {
msg: string = '';
msgType: OutgoingMessageType = 'normal';
owner: WhoAmI = 'user';
socketId: string = '';
}

class QuestionOutgoingMessageDto extends DefaultOutgoingMessageDto {
msg: string = '';
questionId: number = -1;
questionDone: boolean = false;
msgType: OutgoingMessageType = 'question';
socketId: string = '';
}

class QuestionDoneOutgoingMessageDto extends QuestionOutgoingMessageDto {}
class QuestionDoneOutgoingMessageDto extends DefaultOutgoingMessageDto {
msg: string = '';
questionId: number = -1;
questionDone: boolean = false;
msgType: OutgoingMessageType = 'question';
}

class NoticeOutgoingMessageDto extends DefaultOutgoingMessageDto {
msg: string = '';
Expand Down
2 changes: 2 additions & 0 deletions backend/chatServer/src/event/dto/Question.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ class QuestionDto {
roomId: string = '';
nickname: string = '';
color: string = '';
entryTime: string = '';
msg: string = '';
msgTime: string = new Date().toISOString();
msgType: OutgoingMessageType = 'question';
questionId: number = -1;
socketId: string = '';
questionDone: boolean = false;
}

Expand Down
Loading

0 comments on commit 51f7b1c

Please sign in to comment.