From 3bd5503dc8d46e296d2f7a300cdf8e822c03c1bf Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 20:53:37 +0900 Subject: [PATCH 01/67] =?UTF-8?q?refactor:=20`room-socket`=20=EC=84=9C?= =?UTF-8?q?=EB=B8=8C=20=EB=AA=A8=EB=93=88=EC=9D=84=20`websocket`=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 분리하면서 아예 공식 모듈로 승격 - 스터디 세션, 시그널링 서버 핸들러보다 더 상위의 기초적인 서버 설정과 서버 인스턴스를 맡는 전역 게이트웨이 --- .../websocket.entity.ts} | 2 +- backend/src/websocket/websocket.gateway.ts | 51 +++++++++++++++++++ backend/src/websocket/websocket.module.ts | 15 ++++++ .../websocket.repository.ts} | 10 ++-- .../websocket.service.ts} | 23 ++++++--- 5 files changed, 87 insertions(+), 14 deletions(-) rename backend/src/{room/socket/room-socket.entity.ts => websocket/websocket.entity.ts} (81%) create mode 100644 backend/src/websocket/websocket.gateway.ts create mode 100644 backend/src/websocket/websocket.module.ts rename backend/src/{room/socket/room-socket.repository.ts => websocket/websocket.repository.ts} (81%) rename backend/src/{room/socket/room-socket.service.ts => websocket/websocket.service.ts} (60%) diff --git a/backend/src/room/socket/room-socket.entity.ts b/backend/src/websocket/websocket.entity.ts similarity index 81% rename from backend/src/room/socket/room-socket.entity.ts rename to backend/src/websocket/websocket.entity.ts index 806f051e..a6f3b482 100644 --- a/backend/src/room/socket/room-socket.entity.ts +++ b/backend/src/websocket/websocket.entity.ts @@ -1,7 +1,7 @@ import { Entity, Field, Schema } from "nestjs-redis-om"; @Schema("socket") -export class RoomSocketEntity extends Entity { +export class WebsocketEntity extends Entity { @Field({ type: "string", indexed: true }) id: string; diff --git a/backend/src/websocket/websocket.gateway.ts b/backend/src/websocket/websocket.gateway.ts new file mode 100644 index 00000000..a9b88cd9 --- /dev/null +++ b/backend/src/websocket/websocket.gateway.ts @@ -0,0 +1,51 @@ +import { Logger } from "@nestjs/common"; +import { Server, Socket } from "socket.io"; +import { + OnGatewayConnection, + OnGatewayDisconnect, + OnGatewayInit, + WebSocketGateway, + WebSocketServer, +} from "@nestjs/websockets"; +import Redis from "ioredis"; +import { createAdapter } from "@socket.io/redis-adapter"; +import { WebsocketService } from "@/websocket/websocket.service"; +import { WebsocketRepository } from "@/websocket/websocket.repository"; + +@WebSocketGateway() +export class WebsocketGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { + @WebSocketServer() + private server: Server; + + private logger: Logger = new Logger("Websocket"); + + public constructor( + private readonly websocketService: WebsocketService, + private readonly websocketRepository: WebsocketRepository + ) {} + + public afterInit() { + const pubClient = new Redis({ + host: process.env.REDIS_HOST, + port: parseInt(process.env.REDIS_PORT), + }); + + const subClient = pubClient.duplicate(); + + const redisAdapter = createAdapter(pubClient, subClient); + this.server.adapter(redisAdapter); + + this.websocketService.setRedisClient(pubClient); + this.websocketService.setServer(this.server); + } + + public async handleConnection(client: Socket) { + this.logger.log(`Client connected: ${client.id}`); + await this.websocketRepository.register(client); + } + + public async handleDisconnect(client: Socket) { + this.logger.log(`Client disconnected: ${client.id}`); + await this.websocketRepository.clean(client); + } +} diff --git a/backend/src/websocket/websocket.module.ts b/backend/src/websocket/websocket.module.ts new file mode 100644 index 00000000..cf3842fd --- /dev/null +++ b/backend/src/websocket/websocket.module.ts @@ -0,0 +1,15 @@ +import { Global, Module } from "@nestjs/common"; +import { WebsocketService } from "@/websocket/websocket.service"; +import { WebsocketGateway } from "@/websocket/websocket.gateway"; +import { WebsocketRepository } from "@/websocket/websocket.repository"; +import { RedisOmModule } from "nestjs-redis-om"; +import { RoomEntity } from "@/room/room.entity"; +import { WebsocketEntity } from "@/websocket/websocket.entity"; + +@Global() +@Module({ + imports: [RedisOmModule.forFeature([RoomEntity, WebsocketEntity])], + providers: [WebsocketGateway, WebsocketService, WebsocketRepository], + exports: [WebsocketService, WebsocketRepository], +}) +export class WebsocketModule {} diff --git a/backend/src/room/socket/room-socket.repository.ts b/backend/src/websocket/websocket.repository.ts similarity index 81% rename from backend/src/room/socket/room-socket.repository.ts rename to backend/src/websocket/websocket.repository.ts index ba3af2bb..8bd27e59 100644 --- a/backend/src/room/socket/room-socket.repository.ts +++ b/backend/src/websocket/websocket.repository.ts @@ -2,17 +2,17 @@ import { Injectable } from "@nestjs/common"; import { Socket } from "socket.io"; import { InjectRepository } from "nestjs-redis-om"; import { Repository } from "redis-om"; -import { RoomSocketEntity } from "@/room/socket/room-socket.entity"; +import { WebsocketEntity } from "@/websocket/websocket.entity"; @Injectable() -export class RoomSocketRepository { +export class WebsocketRepository { public constructor( - @InjectRepository(RoomSocketEntity) - private readonly socketRepository: Repository + @InjectRepository(WebsocketEntity) + private readonly socketRepository: Repository ) {} public async register(socket: Socket) { - const entity = new RoomSocketEntity(); + const entity = new WebsocketEntity(); entity.id = socket.id; entity.joinedRooms = []; await this.socketRepository.remove(entity.id); diff --git a/backend/src/room/socket/room-socket.service.ts b/backend/src/websocket/websocket.service.ts similarity index 60% rename from backend/src/room/socket/room-socket.service.ts rename to backend/src/websocket/websocket.service.ts index 46b45a21..f5207507 100644 --- a/backend/src/room/socket/room-socket.service.ts +++ b/backend/src/websocket/websocket.service.ts @@ -1,25 +1,32 @@ import { Injectable } from "@nestjs/common"; import { Server } from "socket.io"; +import Redis from "ioredis"; @Injectable() -export class RoomSocketService { +export class WebsocketService { constructor() {} private server: Server; + private redisClient: Redis; + + public setRedisClient(client: Redis) { + this.redisClient = client; + } public setServer(server: Server) { this.server = server; } - public getSocket(socketId: string) { - return this.server.sockets.sockets.get(socketId); + public getRedisClient() { + return this.redisClient; } - public async leaveRoom(socketId: string, roomId: string) { - const socket = this.server.sockets.sockets.get(socketId); - if (socket) { - await socket.leave(roomId); - } + public getServer() { + return this.server; + } + + public getSocket(socketId: string) { + return this.server.sockets.sockets.get(socketId); } public emitToRoom(roomId: string, event: string, data?: any) { From 5f0ae210843773cead97eacc2acd30ef46b71936 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 20:54:05 +0900 Subject: [PATCH 02/67] =?UTF-8?q?refactor:=20=EC=8B=9C=EA=B7=B8=EB=84=90?= =?UTF-8?q?=EB=A7=81=20=EC=84=9C=EB=B2=84=EC=9D=98=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/signaling-server/sig-server.gateway.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/backend/src/signaling-server/sig-server.gateway.ts b/backend/src/signaling-server/sig-server.gateway.ts index 470fa0ea..3a3c909a 100644 --- a/backend/src/signaling-server/sig-server.gateway.ts +++ b/backend/src/signaling-server/sig-server.gateway.ts @@ -1,28 +1,17 @@ import { WebSocketGateway, WebSocketServer, - OnGatewayConnection, - OnGatewayDisconnect, SubscribeMessage, MessageBody, } from "@nestjs/websockets"; import { Server } from "socket.io"; -import { Logger } from "@nestjs/common"; import { EMIT_EVENT, LISTEN_EVENT } from "@/signaling-server/sig-server.event"; @WebSocketGateway() -export class SigServerGateway implements OnGatewayConnection, OnGatewayDisconnect { +export class SigServerGateway { @WebSocketServer() private server: Server; - handleConnection(socket: any) { - Logger.log(`Client connected in signaling server: ${socket.id}`); - } - - handleDisconnect(socket: any) { - Logger.log(`Client disconnected signaling server: ${socket.id}`); - } - @SubscribeMessage(LISTEN_EVENT.OFFER) handleOffer( @MessageBody() From e2322b07a2efdf7c97fe06c0b4c05ef1bb5e6ea9 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 20:54:31 +0900 Subject: [PATCH 03/67] =?UTF-8?q?fix:=20=EC=8A=A4=ED=84=B0=EB=94=94=20?= =?UTF-8?q?=EC=84=B8=EC=85=98=20=EB=82=98=EA=B0=80=EA=B8=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/room/services/room-leave.service.ts | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/backend/src/room/services/room-leave.service.ts b/backend/src/room/services/room-leave.service.ts index f24b68b3..08aa2940 100644 --- a/backend/src/room/services/room-leave.service.ts +++ b/backend/src/room/services/room-leave.service.ts @@ -1,17 +1,17 @@ import { Injectable } from "@nestjs/common"; -import { RoomSocketService } from "@/room/socket/room-socket.service"; +import { WebsocketService } from "@/websocket/websocket.service"; import { RoomRepository } from "@/room/room.repository"; import { RoomDto } from "@/room/dto/room.dto"; import { RoomHostService } from "@/room/services/room-host.service"; -import { EMIT_EVENT } from "@/room/socket/room-socket.events"; +import { EMIT_EVENT } from "@/room/room.events"; import { Socket } from "socket.io"; -import { RoomSocketRepository } from "@/room/socket/room-socket.repository"; +import { WebsocketRepository } from "@/websocket/websocket.repository"; @Injectable() export class RoomLeaveService { constructor( - private readonly socketService: RoomSocketService, - private readonly socketRepository: RoomSocketRepository, + private readonly socketService: WebsocketService, + private readonly socketRepository: WebsocketRepository, private readonly roomRepository: RoomRepository, private readonly roomHostService: RoomHostService ) {} @@ -27,23 +27,17 @@ export class RoomLeaveService { const room = await this.roomRepository.getRoom(roomId); if (!room) return; - await this.leaveSocket(socketId, room); - - if (room.host === socketId) await this.handleHostChange(socketId, room); - else this.socketService.emitToRoom(room.roomId, EMIT_EVENT.QUIT, { socketId }); - - if (!room.connectionList.length) await this.deleteRoom(socketId); - } - - private async leaveSocket(socketId: string, room: RoomDto) { - await this.socketService.leaveRoom(socketId, room.roomId); - - // TODO : 엄청 비효율적인 코드라고 생각하는데 개선할 방법 찾기 room.connectionList = room.connectionList.filter( (connection) => connection.socketId !== socketId ); + if (!room.connectionList.length) return this.deleteRoom(socketId); + await this.roomRepository.setRoom(room); + + if (room.host === socketId) return this.handleHostChange(socketId, room); + + this.socketService.emitToRoom(room.roomId, EMIT_EVENT.QUIT, { socketId }); } private async handleHostChange(socketId: string, room: RoomDto) { From 5477d1e561e0f45d07439396429de7a1ed4cf412 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 20:55:58 +0900 Subject: [PATCH 04/67] =?UTF-8?q?refactor:=20=EC=8A=A4=ED=84=B0=EB=94=94?= =?UTF-8?q?=20=EC=84=B8=EC=85=98=20=EC=9E=85=EC=9E=A5,=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 길어지는 로직을 아예 파일로 분리했음 - 현재로써는 로직의 수가 많지 않기에 가독성 측면에서 분리가 좋다고 판단했습니다. --- .../room-socket.events.ts => room.events.ts} | 0 ...room-socket.gateway.ts => room.gateway.ts} | 56 ++++-------- backend/src/room/room.module.ts | 16 ++-- .../src/room/services/room-create.service.ts | 63 ++++++++++++++ .../src/room/services/room-join.service.ts | 46 ++++++++++ backend/src/room/services/room.service.ts | 86 +------------------ 6 files changed, 135 insertions(+), 132 deletions(-) rename backend/src/room/{socket/room-socket.events.ts => room.events.ts} (100%) rename backend/src/room/{socket/room-socket.gateway.ts => room.gateway.ts} (54%) create mode 100644 backend/src/room/services/room-create.service.ts create mode 100644 backend/src/room/services/room-join.service.ts diff --git a/backend/src/room/socket/room-socket.events.ts b/backend/src/room/room.events.ts similarity index 100% rename from backend/src/room/socket/room-socket.events.ts rename to backend/src/room/room.events.ts diff --git a/backend/src/room/socket/room-socket.gateway.ts b/backend/src/room/room.gateway.ts similarity index 54% rename from backend/src/room/socket/room-socket.gateway.ts rename to backend/src/room/room.gateway.ts index 5c7490f9..40a9b528 100644 --- a/backend/src/room/socket/room-socket.gateway.ts +++ b/backend/src/room/room.gateway.ts @@ -1,62 +1,39 @@ import { WebSocketGateway, - WebSocketServer, - OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, - OnGatewayInit, MessageBody, ConnectedSocket, } from "@nestjs/websockets"; import "dotenv/config"; -import { Server, Socket } from "socket.io"; -import { RoomService } from "../services/room.service"; +import { Socket } from "socket.io"; +import { RoomService } from "./services/room.service"; import { Logger, UsePipes, ValidationPipe } from "@nestjs/common"; -import { EMIT_EVENT, LISTEN_EVENT } from "@/room/socket/room-socket.events"; +import { EMIT_EVENT, LISTEN_EVENT } from "@/room/room.events"; import { CreateRoomDto } from "@/room/dto/create-room.dto"; -import { RoomSocketService } from "@/room/socket/room-socket.service"; +import { WebsocketService } from "@/websocket/websocket.service"; import { JoinRoomDto } from "@/room/dto/join-room.dto"; import { RoomRepository } from "@/room/room.repository"; import { ReactionDto } from "@/room/dto/reaction.dto"; -import { RoomSocketRepository } from "@/room/socket/room-socket.repository"; -import Redis from "ioredis"; -import { createAdapter } from "@socket.io/redis-adapter"; +import { RoomLeaveService } from "@/room/services/room-leave.service"; +import { RoomCreateService } from "@/room/services/room-create.service"; +import { RoomJoinService } from "@/room/services/room-join.service"; @WebSocketGateway() -export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconnect, OnGatewayInit { - @WebSocketServer() - private server: Server; +export class RoomGateway implements OnGatewayDisconnect { + private logger: Logger = new Logger("Room Gateway"); public constructor( private readonly roomService: RoomService, - private readonly socketService: RoomSocketService, - private readonly socketRepository: RoomSocketRepository, + private readonly roomLeaveService: RoomLeaveService, + private readonly roomCreateService: RoomCreateService, + private readonly roomJoinService: RoomJoinService, + private readonly socketService: WebsocketService, private readonly roomRepository: RoomRepository ) {} - public async handleConnection(client: Socket) { - Logger.log(`Client connected: ${client.id}`); - await this.socketRepository.register(client); - } - public async handleDisconnect(client: Socket) { - Logger.log(`Client disconnected: ${client.id}`); await this.handleLeaveRoom(client); - await this.socketRepository.clean(client); - } - - public async afterInit() { - const pubClient = new Redis({ - host: process.env.REDIS_HOST, - port: parseInt(process.env.REDIS_PORT), - }); - - const subClient = pubClient.duplicate(); - - const redisAdapter = createAdapter(pubClient, subClient); - this.server.adapter(redisAdapter); - - this.roomService.setServer(this.server); } @SubscribeMessage(LISTEN_EVENT.CREATE) @@ -65,7 +42,8 @@ export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconne @ConnectedSocket() client: Socket, @MessageBody() dto: CreateRoomDto ) { - await this.roomService.createRoom({ ...dto, socketId: client.id }); + // TODO: try - catch 로 에러 핸들링을 통해 이벤트 Emit 을 여기서 하기 + await this.roomCreateService.createRoom({ ...dto, socketId: client.id }); } @SubscribeMessage(LISTEN_EVENT.JOIN) @@ -74,12 +52,12 @@ export class RoomSocketGateway implements OnGatewayConnection, OnGatewayDisconne @ConnectedSocket() client: Socket, @MessageBody() dto: JoinRoomDto ) { - await this.roomService.joinRoom({ ...dto, socketId: client.id }); + await this.roomJoinService.joinRoom({ ...dto, socketId: client.id }); } @SubscribeMessage(LISTEN_EVENT.LEAVE) public async handleLeaveRoom(client: Socket) { - await this.roomService.leaveRoom(client); + await this.roomLeaveService.leaveRoom(client); } @SubscribeMessage(LISTEN_EVENT.FINISH) diff --git a/backend/src/room/room.module.ts b/backend/src/room/room.module.ts index e58dd357..cd1a0574 100644 --- a/backend/src/room/room.module.ts +++ b/backend/src/room/room.module.ts @@ -1,27 +1,27 @@ import { Module } from "@nestjs/common"; import { RoomService } from "./services/room.service"; -import { RoomSocketGateway } from "./socket/room-socket.gateway"; +import { RoomGateway } from "./room.gateway"; import { RoomRepository } from "./room.repository"; import { RoomController } from "./room.controller"; import { RedisOmModule } from "nestjs-redis-om"; import { RoomEntity } from "./room.entity"; import { RoomLeaveService } from "@/room/services/room-leave.service"; -import { RoomSocketService } from "@/room/socket/room-socket.service"; import { RoomHostService } from "@/room/services/room-host.service"; -import { RoomSocketRepository } from "@/room/socket/room-socket.repository"; -import { RoomSocketEntity } from "@/room/socket/room-socket.entity"; import { QuestionListRepository } from "@/question-list/question-list.repository"; +import { WebsocketModule } from "@/websocket/websocket.module"; +import { RoomCreateService } from "@/room/services/room-create.service"; +import { RoomJoinService } from "@/room/services/room-join.service"; @Module({ - imports: [RedisOmModule.forFeature([RoomEntity, RoomSocketEntity])], + imports: [RedisOmModule.forFeature([RoomEntity]), WebsocketModule], providers: [ RoomService, - RoomSocketGateway, + RoomGateway, RoomRepository, + RoomCreateService, + RoomJoinService, RoomLeaveService, - RoomSocketService, RoomHostService, - RoomSocketRepository, QuestionListRepository, ], controllers: [RoomController], diff --git a/backend/src/room/services/room-create.service.ts b/backend/src/room/services/room-create.service.ts new file mode 100644 index 00000000..9df2e787 --- /dev/null +++ b/backend/src/room/services/room-create.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from "@nestjs/common"; +import { CreateRoomInternalDto } from "@/room/dto/create-room.dto"; +import { EMIT_EVENT } from "@/room/room.events"; +import { WebsocketService } from "@/websocket/websocket.service"; +import { RoomRepository } from "@/room/room.repository"; +import { QuestionListRepository } from "@/question-list/question-list.repository"; +import { RoomJoinService } from "@/room/services/room-join.service"; +import { createHash } from "node:crypto"; +import "dotenv/config"; + +@Injectable() +export class RoomCreateService { + private static ROOM_ID_CREATE_KEY = "room_id"; + + public constructor( + private readonly roomRepository: RoomRepository, + private readonly socketService: WebsocketService, + private readonly questionListRepository: QuestionListRepository, + private readonly roomJoinService: RoomJoinService + ) {} + + public async createRoom(dto: CreateRoomInternalDto) { + const { socketId, nickname } = dto; + const roomId = await this.generateRoomId(); + const currentTime = Date.now(); + const questionListContents = await this.questionListRepository.getContentsByQuestionListId( + dto.questionListId + ); + + const roomDto = { + ...dto, + roomId, + connectionList: [], + questionListContents, + createdAt: currentTime, + host: dto.socketId, + }; + + await this.roomRepository.setRoom(roomDto); + + await this.roomJoinService.joinRoom({ roomId, socketId, nickname }); + + this.socketService.emitToRoom(roomId, EMIT_EVENT.CREATE, roomDto); + } + + // TODO: 동시성 고려해봐야하지 않을까? + private async generateRoomId() { + const client = this.socketService.getRedisClient(); + + const idString = await client.get(RoomCreateService.ROOM_ID_CREATE_KEY); + + let id: number; + if (idString && !isNaN(parseInt(idString))) { + id = await client.incr(RoomCreateService.ROOM_ID_CREATE_KEY); + } else { + id = parseInt(await client.set(RoomCreateService.ROOM_ID_CREATE_KEY, "1")); + } + + return createHash("sha256") + .update(id + process.env.SESSION_HASH) + .digest("hex"); + } +} diff --git a/backend/src/room/services/room-join.service.ts b/backend/src/room/services/room-join.service.ts new file mode 100644 index 00000000..32d41eff --- /dev/null +++ b/backend/src/room/services/room-join.service.ts @@ -0,0 +1,46 @@ +import { Injectable } from "@nestjs/common"; +import { EMIT_EVENT } from "@/room/room.events"; +import { WebsocketService } from "@/websocket/websocket.service"; +import { RoomRepository } from "@/room/room.repository"; +import { RoomDto } from "@/room/dto/room.dto"; +import { JoinRoomInternalDto } from "@/room/dto/join-room.dto"; +import { WebsocketRepository } from "@/websocket/websocket.repository"; + +@Injectable() +export class RoomJoinService { + public constructor( + private readonly roomRepository: RoomRepository, + private readonly socketService: WebsocketService, + private readonly socketRepository: WebsocketRepository + ) {} + + public async joinRoom(dto: JoinRoomInternalDto) { + const { roomId, socketId, nickname } = dto; + + const room = await this.roomRepository.getRoom(roomId); + const socket = this.socketService.getSocket(socketId); + + if (!socket) throw new Error("Invalid Socket"); + + if (room.roomId === null) throw new Error("Redis: RoomEntity Entity type error"); + + if (this.isFullRoom(room)) return socket.emit(EMIT_EVENT.FULL, {}); + + socket.join(roomId); + await this.socketRepository.joinRoom(socket.id, roomId); + room.connectionList.push({ + socketId, + createAt: Date.now(), + nickname, + }); + + await this.roomRepository.setRoom(room); + + // TODO: 성공 / 실패 여부를 전송하는데 있어서 결과에 따라 다르게 해야하는데.. 어떻게 관심 분리를 할까? + this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room); + } + + private isFullRoom(room: RoomDto): boolean { + return room.maxParticipants <= room.connectionList.length; + } +} diff --git a/backend/src/room/services/room.service.ts b/backend/src/room/services/room.service.ts index 880cb817..012ea881 100644 --- a/backend/src/room/services/room.service.ts +++ b/backend/src/room/services/room.service.ts @@ -1,30 +1,10 @@ import { Injectable } from "@nestjs/common"; import { RoomStatus } from "@/room/room.entity"; -import { Server, Socket } from "socket.io"; -import { CreateRoomInternalDto } from "@/room/dto/create-room.dto"; -import { generateRoomId } from "@/utils/generateRoomId"; -import { EMIT_EVENT } from "@/room/socket/room-socket.events"; -import { RoomSocketService } from "@/room/socket/room-socket.service"; -import { RoomLeaveService } from "@/room/services/room-leave.service"; import { RoomRepository } from "@/room/room.repository"; -import { RoomDto } from "@/room/dto/room.dto"; -import { JoinRoomInternalDto } from "@/room/dto/join-room.dto"; -import { RoomSocketRepository } from "@/room/socket/room-socket.repository"; -import { QuestionListRepository } from "@/question-list/question-list.repository"; @Injectable() export class RoomService { - public constructor( - private readonly roomRepository: RoomRepository, - private readonly roomLeaveService: RoomLeaveService, - private readonly socketService: RoomSocketService, - private readonly socketRepository: RoomSocketRepository, - private readonly questionListRepository: QuestionListRepository - ) {} - - public setServer(server: Server) { - this.socketService.setServer(server); - } + public constructor(private readonly roomRepository: RoomRepository) {} public async getPublicRoom() { const rooms = await this.roomRepository.getAllRoom(); @@ -33,70 +13,6 @@ export class RoomService { .sort((a, b) => b.createdAt - a.createdAt); } - public async leaveRoom(socket: Socket) { - return this.roomLeaveService.leaveRoom(socket); - } - - public async createRoom(dto: CreateRoomInternalDto) { - const roomId = generateRoomId(); - const currentTime = Date.now(); - - const roomDto = { - ...dto, - roomId, - connectionList: [ - { - socketId: dto.socketId, - createAt: currentTime, - nickname: dto.nickname, - }, - ], - questionListContents: await this.questionListRepository.getContentsByQuestionListId( - dto.questionListId - ), - createdAt: currentTime, - host: dto.socketId, - }; - await this.roomRepository.setRoom(roomDto); - - const socket = this.socketService.getSocket(dto.socketId); - socket.join(roomId); - await this.socketRepository.joinRoom(socket.id, roomId); - this.socketService.emitToRoom(roomId, EMIT_EVENT.CREATE, roomDto); - } - - public async joinRoom(dto: JoinRoomInternalDto) { - const { roomId, socketId, nickname } = dto; - - const room = await this.roomRepository.getRoom(roomId); - const socket = this.socketService.getSocket(socketId); - - if (!socket) throw new Error("Invalid Socket"); - - if (room.roomId === null) throw new Error("Redis: RoomEntity Entity type error"); - - // TODO : 에러를 보내기 -> Exception 생성해서 분류 - if (this.isFullRoom(room)) return socket.emit(EMIT_EVENT.FULL, {}); - - // TODO : join room 하는 단계가 3가지가 같이 혼재 -> 이것만 따로 묶어보기? - socket.join(roomId); - await this.socketRepository.joinRoom(socket.id, roomId); - room.connectionList.push({ - socketId, - createAt: Date.now(), - nickname, - }); - - await this.roomRepository.setRoom(room); - - // TODO: 성공 / 실패 여부를 전송하는데 있어서 결과에 따라 다르게 해야하는데.. 어떻게 관심 분리를 할까? - this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room); - } - - private isFullRoom(room: RoomDto): boolean { - return room.maxParticipants <= room.connectionList.length; - } - public async finishRoom(roomId: string) { await this.roomRepository.removeRoom(roomId); return roomId; From a9c189712da5a68b1dc2f1f588c4899298929ef8 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 20:56:44 +0900 Subject: [PATCH 05/67] =?UTF-8?q?fix:=20=EC=97=AC=EB=9F=AC=20WAS=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=8A=A4=ED=84=B0=EB=94=94=EC=84=B8=EC=85=98?= =?UTF-8?q?=EC=9D=84=20=EC=9E=98=EB=AA=BB=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `backend/src/room/services/room-create.service.ts` 해당 파일에서 새롭게 `redisClient` 를 받아와서 새로운 roomId 값을 생성하도록 개선 --- backend/src/utils/generateRoomId.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 backend/src/utils/generateRoomId.ts diff --git a/backend/src/utils/generateRoomId.ts b/backend/src/utils/generateRoomId.ts deleted file mode 100644 index c808594a..00000000 --- a/backend/src/utils/generateRoomId.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createHash } from "node:crypto"; -import "dotenv/config"; - -let id = parseInt(process.env.SESSION_HASH_START); -export function generateRoomId() { - return createHash("sha256") - .update(id++ + process.env.SESSION_HASH) - .digest("hex"); -} \ No newline at end of file From b9fe581474dfd27a1b406aad7b76c9f2ac535a44 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 21:10:10 +0900 Subject: [PATCH 06/67] =?UTF-8?q?fix:=20`create=5Froom`=20=EC=8B=9C,=20`jo?= =?UTF-8?q?in`=20emit=EA=B9=8C=EC=A7=80=20=EB=B0=9B=EB=8A=94=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/room/services/room-create.service.ts | 2 +- backend/src/room/services/room-join.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/room/services/room-create.service.ts b/backend/src/room/services/room-create.service.ts index 9df2e787..74f2c8c7 100644 --- a/backend/src/room/services/room-create.service.ts +++ b/backend/src/room/services/room-create.service.ts @@ -38,7 +38,7 @@ export class RoomCreateService { await this.roomRepository.setRoom(roomDto); - await this.roomJoinService.joinRoom({ roomId, socketId, nickname }); + await this.roomJoinService.joinRoom({ roomId, socketId, nickname }, true); this.socketService.emitToRoom(roomId, EMIT_EVENT.CREATE, roomDto); } diff --git a/backend/src/room/services/room-join.service.ts b/backend/src/room/services/room-join.service.ts index 32d41eff..f242b15f 100644 --- a/backend/src/room/services/room-join.service.ts +++ b/backend/src/room/services/room-join.service.ts @@ -14,7 +14,7 @@ export class RoomJoinService { private readonly socketRepository: WebsocketRepository ) {} - public async joinRoom(dto: JoinRoomInternalDto) { + public async joinRoom(dto: JoinRoomInternalDto, isCreate: boolean = false) { const { roomId, socketId, nickname } = dto; const room = await this.roomRepository.getRoom(roomId); @@ -37,7 +37,7 @@ export class RoomJoinService { await this.roomRepository.setRoom(room); // TODO: 성공 / 실패 여부를 전송하는데 있어서 결과에 따라 다르게 해야하는데.. 어떻게 관심 분리를 할까? - this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room); + if (!isCreate) this.socketService.emitToRoom(roomId, EMIT_EVENT.JOIN, room); } private isFullRoom(room: RoomDto): boolean { From ca2f744950fc2ae5df6b7062ae22927d37d25501 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 21:31:39 +0900 Subject: [PATCH 07/67] =?UTF-8?q?fix:=20=EB=A7=88=EC=A7=80=EB=A7=89=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EA=B0=80=20=EB=B0=A9=20=ED=87=B4?= =?UTF-8?q?=EC=9E=A5=20=EC=8B=9C=20=EC=82=AD=EC=A0=9C=EB=90=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8D=98=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/room/services/room-leave.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/room/services/room-leave.service.ts b/backend/src/room/services/room-leave.service.ts index 08aa2940..aaafbd1c 100644 --- a/backend/src/room/services/room-leave.service.ts +++ b/backend/src/room/services/room-leave.service.ts @@ -31,7 +31,7 @@ export class RoomLeaveService { (connection) => connection.socketId !== socketId ); - if (!room.connectionList.length) return this.deleteRoom(socketId); + if (!room.connectionList.length) return this.deleteRoom(room.roomId); await this.roomRepository.setRoom(room); From 99ee9098146788240a17aec1756502797fc0cff4 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 21:31:54 +0900 Subject: [PATCH 08/67] =?UTF-8?q?chore:=20docker=20compose=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이제 redis-stack을 사용 --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9370a4b2..93525498 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,10 +17,10 @@ services: - ./mysql-data:/var/lib/mysql redis: - image: redis:latest - container_name: redis + image: redis/redis-stack:latest ports: - - '6379:6379' - command: redis-server --port 6379 + - "6379:6379" # Redis port + - "8001:8001" # RedisInsight port volumes: - - ./redis-data:/data \ No newline at end of file + - redis_data:/data + restart: unless-stopped \ No newline at end of file From da6e3ab95e23d0da391aea5812c84c561fce4057 Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 22:05:19 +0900 Subject: [PATCH 09/67] =?UTF-8?q?fix:=20docker-compose.yml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 93525498..b35239e0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,11 +16,12 @@ services: volumes: - ./mysql-data:/var/lib/mysql - redis: + redis-stack: image: redis/redis-stack:latest + container_name: redis-stack ports: - - "6379:6379" # Redis port - - "8001:8001" # RedisInsight port + - "6379:6379" + - "8001:8001" volumes: - - redis_data:/data - restart: unless-stopped \ No newline at end of file + - ./redis-stack.conf:/usr/local/etc/redis/redis-stack.conf + - ./redis-stack-data/:/data \ No newline at end of file From 12a9e8379495bf0335614ec488cdb153068bc7ce Mon Sep 17 00:00:00 2001 From: Chanwoo Kim Date: Mon, 25 Nov 2024 22:37:11 +0900 Subject: [PATCH 10/67] =?UTF-8?q?fix:=20docker-compose.yml=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 설정 파일이 존재하지않으므로 설정하지 않도록 변경 - .gitignore 에 맞게 컨테이너 폴더 이름 변경 --- docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b35239e0..aae58ddb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,5 +23,4 @@ services: - "6379:6379" - "8001:8001" volumes: - - ./redis-stack.conf:/usr/local/etc/redis/redis-stack.conf - - ./redis-stack-data/:/data \ No newline at end of file + - ./redis-data/:/data \ No newline at end of file From de772003eb65bf6b267e6097a48c67d5493759c6 Mon Sep 17 00:00:00 2001 From: JeongwooSeo Date: Mon, 25 Nov 2024 23:12:24 +0900 Subject: [PATCH 11/67] =?UTF-8?q?refactor:=20og-image=EC=9A=A9=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20svg=EA=B0=80=20=EC=95=84=EB=8B=8C=20jpg=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 25 ++++++++++++++++++++----- frontend/public/snowman-thumbnail.jpg | Bin 0 -> 75275 bytes 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 frontend/public/snowman-thumbnail.jpg diff --git a/frontend/index.html b/frontend/index.html index bf4aa749..333f6503 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -6,17 +6,32 @@ PREVIEW - 실시간 면접 스터디 플랫폼 - - + + - - + + - + diff --git a/frontend/public/snowman-thumbnail.jpg b/frontend/public/snowman-thumbnail.jpg new file mode 100644 index 0000000000000000000000000000000000000000..98412b04c7e7c61ab567bc6dbeb5d4c1ac8e49e2 GIT binary patch literal 75275 zcmbrk1#~36vY=UJW@ct)W~MeXwV4^)*k-od%yyfZnVFfHnVD()zdQTx-92Y!_sm9C z>3kIt%2Y|ploYA2<*#i3qO`b_H~M}PgzsD@VmFfiyp z?D$>4Z^rzG&Hlx-|K;;97W{{eZETIdef}x4y|In)KivP#p{_0_-wY!A&B3k~CT`z6 z|IMV^)L+5d91Ff(^1q%@);Wa8rDB$PC9vo>*drc*F5vNCWqCKR!=u{W^&1pxf(KmU^p zfc#J05`I@QI};Z>I~_CqclH0b{BMo_?e)Khf7b0OkN2fFr;S;05pl1Op-fe*lSqG(a{WA5a3Q1k?hW0PTPtz#w1@Fb!A$ ztO2$G2Y@rcHQ*8O_WdA(0D=QT0m1?z03rjT0b&N?1mXu01Cj+&0@4Kf31kLj3*-Xi z1rz`j0Tc(629yI-0#pOk0@MRE0yG1(0<;5k0(1-X3Jd}a3ycbk2TTsk0L%$21S|ur z0;~&c2J8Ur0UQV%4V(g;3tSG|2;2ia2D|{g4SWXt00IDk0YL{L0-*!p1Q7v|2hjpC z1#txN0SO050?7rb1Zf2s0+|EZ1~~_L0R;y|0VM>b1LXmg1XTq!0(AiO0gVJr11$z^ z1nmc%1Kk0=2K@ws1H%QQ0pkXf0@DC919Jll21^1f1Zw~r09yb%0DA-n2S*2|0OtUg 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 Date: Mon, 25 Nov 2024 23:15:18 +0900 Subject: [PATCH 12/67] =?UTF-8?q?refactor:=20deprecated=EB=90=9C=20?= =?UTF-8?q?=ED=83=9C=EA=B7=B8=20=EB=8C=80=EC=B2=B4=20=ED=83=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 333f6503..0558478c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -27,7 +27,7 @@ content="https://boostcamp-preview.kro.kr/snowman-thumbnail.jpg" /> - + Date: Mon, 25 Nov 2024 23:20:27 +0900 Subject: [PATCH 13/67] =?UTF-8?q?feat:=20user=5Fquestion=5Flist=20?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/question-list/question-list.entity.ts | 7 ++++-- backend/src/user/user.entity.ts | 24 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/backend/src/question-list/question-list.entity.ts b/backend/src/question-list/question-list.entity.ts index 829469a7..0ceee261 100644 --- a/backend/src/question-list/question-list.entity.ts +++ b/backend/src/question-list/question-list.entity.ts @@ -7,7 +7,7 @@ import { ManyToMany, JoinTable, } from "typeorm"; -import { User } from "../user/user.entity"; +import { User } from "@/user/user.entity"; import { Question } from "./question.entity"; import { Category } from "./category.entity"; @@ -42,9 +42,12 @@ export class QuestionList { @JoinTable({ name: "question_list_category", joinColumn: { - name: "questionListId", + name: "question_list_id", referencedColumnName: "id", }, }) categories: Category[]; + + @ManyToMany(() => User, (user) => user.scrappedQuestionLists) + scrappedByUsers: User[]; } diff --git a/backend/src/user/user.entity.ts b/backend/src/user/user.entity.ts index 6fbcc9e1..3832c258 100644 --- a/backend/src/user/user.entity.ts +++ b/backend/src/user/user.entity.ts @@ -1,5 +1,12 @@ -import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from "typeorm"; -import { QuestionList } from "../question-list/question-list.entity"; +import { + Entity, + PrimaryGeneratedColumn, + Column, + OneToMany, + ManyToMany, + JoinTable, +} from "typeorm"; +import { QuestionList } from "@/question-list/question-list.entity"; @Entity() export class User { @@ -28,4 +35,17 @@ export class User { @OneToMany(() => QuestionList, (questionList) => questionList.user) questionLists: QuestionList[]; + + @ManyToMany( + () => QuestionList, + (questionList) => questionList.scrappedByUsers + ) + @JoinTable({ + name: "user_question_list", + joinColumn: { + name: "user_id", + referencedColumnName: "id", + }, + }) + scrappedQuestionLists: QuestionList[]; } From d5f9863b2d0ee84663e491e7d3681e6bdc454eb7 Mon Sep 17 00:00:00 2001 From: yiseungyun Date: Tue, 26 Nov 2024 00:38:20 +0900 Subject: [PATCH 14/67] =?UTF-8?q?feat:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=EB=B0=94=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/common/Sidebar.tsx | 87 +++++-------------- .../src/components/common/SidebarMenu.tsx | 42 +++++++++ frontend/src/components/common/routesData.tsx | 52 +++++++++++ frontend/src/hooks/useAuth.ts | 3 +- pnpm-lock.yaml | 42 +-------- 5 files changed, 121 insertions(+), 105 deletions(-) create mode 100644 frontend/src/components/common/SidebarMenu.tsx create mode 100644 frontend/src/components/common/routesData.tsx diff --git a/frontend/src/components/common/Sidebar.tsx b/frontend/src/components/common/Sidebar.tsx index 66b00b0c..5b628592 100644 --- a/frontend/src/components/common/Sidebar.tsx +++ b/frontend/src/components/common/Sidebar.tsx @@ -1,46 +1,32 @@ -import { Link } from "react-router-dom"; -import { ReactElement, useEffect, useState } from "react"; -import { FaClipboardList, FaLayerGroup } from "react-icons/fa"; -import { MdDarkMode, MdLightMode, MdLogout } from "react-icons/md"; -import { IoPersonSharp, IoHomeSharp } from "react-icons/io5"; +import { useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { MdDarkMode, MdLightMode } from "react-icons/md"; import { FaGithub } from "react-icons/fa6"; import useTheme from "@hooks/useTheme.ts"; +import useAuth from "@/hooks/useAuth"; +import { authenticatedRoutes, Route, unauthenticatedRoutes } from "./routesData"; +import SidebarMenu from "./SidebarMenu"; const Sidebar = () => { - const routes = [ - { - path: "/", - label: "홈", - icon: , - }, - { - path: "/questions", - label: "질문지 리스트", - icon: , - }, - { - path: "/sessions", - label: "스터디 세션 목록", - icon: , - }, - { - path: "/mypage", - label: "마이페이지", - icon: , - }, - { - path: "/logout", - label: "로그아웃", - icon: , - }, - ]; - + const { isLoggedIn, logOut } = useAuth(); const [selected, setSelected] = useState(""); + const [currentRoutes, setCurrentRoutes] = useState([]); const { theme, toggleTheme } = useTheme(); + const navigate = useNavigate(); useEffect(() => { setSelected(window.location.pathname); }, []); + + const logoutHandler = () => { + logOut(); + navigate("/"); + } + + useEffect(() => { + setCurrentRoutes(isLoggedIn ? authenticatedRoutes(logoutHandler) : unauthenticatedRoutes); + }, [isLoggedIn]); + return (