diff --git a/main-project/src/boards/repository/board-guest-team.repository.ts b/main-project/src/boards/repository/board-guest-team.repository.ts index d944b2b3..edf8a4e8 100644 --- a/main-project/src/boards/repository/board-guest-team.repository.ts +++ b/main-project/src/boards/repository/board-guest-team.repository.ts @@ -1,5 +1,6 @@ import { InternalServerErrorException } from '@nestjs/common'; import { ResultSetHeader } from 'mysql2'; +import { BoardGuestTeam } from 'src/chats/interface/chat.interface'; import { EntityRepository, InsertResult, @@ -127,4 +128,21 @@ export class BoardGuestTeamsRepository extends Repository { ); } } + + async getGuestTeams(boardNo: number): Promise { + try { + const guestTeam: BoardGuestTeam[] = await this.createQueryBuilder( + 'board_guest_teams', + ) + .select(['board_guest_teams.no as teamNo']) + .where('board_guest_teams.board_no = :boardNo', { boardNo }) + .getRawMany(); + + return guestTeam; + } catch (error) { + throw new InternalServerErrorException( + `${error} getGuestTeam: 알 수 없는 서버 에러입니다.`, + ); + } + } } diff --git a/main-project/src/boards/repository/board.repository.ts b/main-project/src/boards/repository/board.repository.ts index f89fca63..80e5b368 100644 --- a/main-project/src/boards/repository/board.repository.ts +++ b/main-project/src/boards/repository/board.repository.ts @@ -288,15 +288,16 @@ export class BoardsRepository extends Repository { async getUsersByBoardNo( boardNo: number, userNo: number, + guestTeamNo, ): Promise { try { const users: ChatRoomOfBoard = await this.createQueryBuilder('boards') .leftJoin('boards.hosts', 'hostTeam') - .leftJoin('boards.teamNo', 'team') - .leftJoin('team.boardGuest', 'guestTeam') .leftJoin('hostTeam.userNo', 'hostUser') - .leftJoin('guestTeam.userNo', 'guestUser') .leftJoin('hostUser.userProfileNo', 'hostProfile') + .leftJoin('boards.teamNo', 'guestTeams') + .leftJoin('guestTeams.boardGuest', 'guestTeam') + .leftJoin('guestTeam.userNo', 'guestUser') .leftJoin('guestUser.userProfileNo', 'guestProfile') .select([ 'boards.no AS boardNo', @@ -305,10 +306,17 @@ export class BoardsRepository extends Repository { 'GROUP_CONCAT(DISTINCT hostTeam.user_no) AS hostsUserNo', 'GROUP_CONCAT(DISTINCT guestTeam.user_no) AS guestsUserNo', ]) - .where('boards.no = :boardNo AND boards.user_no = :userNo', { - boardNo, - userNo, - }) + .where( + `boards.no = :boardNo + AND boards.user_no = :userNo + AND guestTeam.no = :guestTeamNo + `, + { + boardNo, + userNo, + guestTeamNo, + }, + ) .getRawOne(); return users; diff --git a/main-project/src/chats/chats-controller.service.ts b/main-project/src/chats/chats-controller.service.ts index 0bd7f0b9..71ccf9f2 100644 --- a/main-project/src/chats/chats-controller.service.ts +++ b/main-project/src/chats/chats-controller.service.ts @@ -5,18 +5,24 @@ import { NotFoundException, } from '@nestjs/common'; import { ResultSetHeader } from 'mysql2'; +import { Boards } from 'src/boards/entity/board.entity'; +import { BoardGuestTeamsRepository } from 'src/boards/repository/board-guest-team.repository'; +import { BoardsRepository } from 'src/boards/repository/board.repository'; import { NoticeType } from 'src/common/configs/notice-type.config'; import { UserType } from 'src/common/configs/user-type.config'; import { NoticeChats } from 'src/notices/entity/notice-chat.entity'; import { NoticeChatsRepository } from 'src/notices/repository/notices-chats.repository'; import { NoticesRepository } from 'src/notices/repository/notices.repository'; -import { EntityManager } from 'typeorm'; +import { EntityManager, Timestamp } from 'typeorm'; import { AcceptInvitationDto } from './dto/accept-invitation.dto'; import { ChatList } from './entity/chat-list.entity'; import { ChatLog } from './entity/chat-log.entity'; import { - ChatRoom, + BoardGuestTeam, + ChatRoomBeforeCreate, ChatRoomInvitation, + ChatRoomOfBoard, + ChatRoomWithUsers, ChatUser, ChatUserValidation, } from './interface/chat.interface'; @@ -31,7 +37,163 @@ export class ChatsControllerService { private readonly chatLogRepository: ChatLogRepository, private readonly chatListRepository: ChatListRepository, private readonly noticeChatsRepository: NoticeChatsRepository, + private readonly boardRepository: BoardsRepository, + private readonly boardGuestTeamsRepository: BoardGuestTeamsRepository, ) {} + async createChatRoom( + userNo: number, + manager: EntityManager, + boardNo: number, + guestTeamNo: number, + ): Promise { + await this.checkChatRoomExists(userNo, boardNo, guestTeamNo); + + const { roomName, hostsUserNo, guestsUserNo } = + await this.getUsersByBoardNo(boardNo, userNo, guestTeamNo); + + const chatRoomNo: number = await this.createChatRoomByBoardNo(manager, { + boardNo, + roomName, + }); + + const chatUsers: number[][] = await Promise.all([ + await this.setChatRoomUsers(manager, { + users: hostsUserNo, + userType: UserType.HOST, + chatRoomNo, + }), + await this.setChatRoomUsers(manager, { + users: guestsUserNo, + userType: UserType.GUEST, + chatRoomNo, + }), + ]); + + const users: number[] = chatUsers.flat(); + + await Promise.all( + users.map( + async (receiverNo) => + await this.createChatRoomNotice(receiverNo, userNo, manager), + ), + ); + } + + private async createChatRoomNotice( + userNo: number, + targetUserNo: number, + manager: EntityManager, + ) { + await manager.getCustomRepository(NoticesRepository).saveNotice({ + userNo, + targetUserNo, + type: NoticeType.CHAT_ROOM_CREATED, + }); + } + + private async checkChatRoomExists( + userNo: number, + boardNo: number, + guestTeamNo: number, + ): Promise { + const board: Boards = await this.boardRepository.getBoard(boardNo); + if (!board) { + throw new NotFoundException('게시물을 찾지 못했습니다.'); + } + if (board.userNo !== userNo) { + throw new BadRequestException('게시글의 작성자만 수락할 수 있습니다.'); + } + + const guestTeams: BoardGuestTeam[] = + await this.boardGuestTeamsRepository.getGuestTeams(boardNo); + if (!guestTeams[0]) { + throw new NotFoundException(`여름 요청이 존재하지 않습니다.`); + } + + const matchTeamNo: boolean = guestTeams.some( + (guestTeam) => guestTeam.teamNo === guestTeamNo, + ); + if (!matchTeamNo) { + throw new NotFoundException(`일치하는 여름 요청이 없습니다.`); + } + + const chatRoom: ChatList = + await this.chatListRepository.getChatRoomByBoardNo(boardNo); + if (chatRoom) { + throw new BadRequestException('이미 생성된 채팅방 입니다.'); + } + } + + private async getUsersByBoardNo( + boardNo: number, + userNo: number, + guestTeamNo: number, + ): Promise { + const chatUsersOfBoard: ChatRoomOfBoard = + await this.boardRepository.getUsersByBoardNo( + boardNo, + userNo, + guestTeamNo, + ); + + const chatRoom: ChatRoomOfBoard = this.setChatRoomName(chatUsersOfBoard); + + return chatRoom; + } + + private setChatRoomName(chatRoom: ChatRoomOfBoard): ChatRoomOfBoard { + chatRoom.roomName = chatRoom.guestsNickname + ',' + chatRoom.hostsNickname; + + return chatRoom; + } + + private async createChatRoomByBoardNo( + manager: EntityManager, + chatRoom: ChatRoomBeforeCreate, + ): Promise { + const createResult: number = await manager + .getCustomRepository(ChatListRepository) + .createChatRoom(chatRoom); + if (!createResult) { + throw new InternalServerErrorException(`채팅방 생성 오류입니다.`); + } + + return createResult; + } + + private async setChatRoomUsers( + manager: EntityManager, + chatRoomUsers: ChatRoomWithUsers, + ): Promise { + const { userType, chatRoomNo }: ChatRoomWithUsers = chatRoomUsers; + const users: number[] = chatRoomUsers.users.split(',').map(Number); + + const chatUsers: ChatUser[] = users.reduce((values, userNo) => { + values.push({ chatRoomNo, userNo, userType }); + + return values; + }, []); + + await this.createChatUsers(manager, chatUsers); + return users; + } + + private async createChatUsers( + manager: EntityManager, + chatUsers: ChatUser[], + ): Promise { + const insertResult: number = await manager + .getCustomRepository(ChatUsersRepository) + .createChatUsers(chatUsers); + + if (!insertResult) { + throw new InternalServerErrorException( + '채팅방 유저정보 생성 오류입니다.', + ); + } + + return insertResult; + } async getPreviousChatLog( userNo: number, @@ -69,15 +231,6 @@ export class ChatsControllerService { return currentChatLog; } - private async checkChatRoomExists(chatRoomNo: number): Promise { - const chatRoom: ChatList = await this.chatListRepository.getChatRoomByNo( - chatRoomNo, - ); - if (!chatRoom) { - throw new NotFoundException('존재하지 않는 채팅방입니다.'); - } - } - private async checkUserInChatRoom({ userNo, chatRoomNo, @@ -124,7 +277,6 @@ export class ChatsControllerService { chatRoomNo, isNeededUser: false, }); - console.log(inviter); await this.saveNotice(manager, { userNo, diff --git a/main-project/src/chats/chats-gateway.service.ts b/main-project/src/chats/chats-gateway.service.ts index c9fbc7ee..7c691740 100644 --- a/main-project/src/chats/chats-gateway.service.ts +++ b/main-project/src/chats/chats-gateway.service.ts @@ -47,76 +47,6 @@ export class ChatsGatewayService { return chatRooms; } - async createRoom( - manager: EntityManager, - socket: Socket, - userNo: number, - messagePayload: CreateChatDto, - ): Promise { - const { boardNo } = messagePayload; - - await this.checkChatRoomExists(boardNo, userNo); - - const { roomName, hostsUserNo, guestsUserNo } = - await this.getUsersByBoardNo(boardNo, userNo); - const chatRoomNo: number = await this.createChatRoom(manager, { - boardNo, - roomName, - }); - - await this.setChatRoomUsers(manager, { - users: hostsUserNo, - userType: UserType.HOST, - chatRoomNo, - }); - - await this.setChatRoomUsers(manager, { - users: guestsUserNo, - userType: UserType.GUEST, - chatRoomNo, - }); - - socket.join(`${chatRoomNo}`); - - return { chatRoomNo, roomName }; - } - - private async setChatRoomUsers( - manager: EntityManager, - chatRoomUsers: ChatRoomWithUsers, - ): Promise { - const { userType, chatRoomNo }: ChatRoomWithUsers = chatRoomUsers; - const users = chatRoomUsers.users.split(',').map(Number); - - const chatUsers: ChatUser[] = users.reduce((values, userNo) => { - values.push({ chatRoomNo, userNo, userType }); - - return values; - }, []); - - await this.createChatUsers(manager, chatUsers); - } - - private async checkChatRoomExists( - boardNo: number, - userNo: number, - ): Promise { - const board: Boards = await this.boardRepository.getBoard(boardNo); - if (!board) { - throw new NotFoundException('게시물을 찾지 못했습니다.'); - } - - if (board.userNo !== userNo) { - throw new BadRequestException('게시글의 작성자만 수락할 수 있습니다.'); - } - - const chatRoom: ChatList = - await this.chatListRepository.getChatRoomByBoardNo(boardNo); - if (chatRoom) { - throw new BadRequestException('이미 생성된 채팅방 입니다.'); - } - } - async getChatRooms(userNo: number): Promise { const rooms = await this.chatUsersRepository.getChatRoomNoByUserNo(userNo); if (!rooms) { @@ -218,56 +148,6 @@ export class ChatsGatewayService { } } - private async getUsersByBoardNo( - boardNo: number, - userNo: number, - ): Promise { - const chatUsersOfBoard: ChatRoomOfBoard = - await this.boardRepository.getUsersByBoardNo(boardNo, userNo); - if (!chatUsersOfBoard) { - throw new NotFoundException('유저 조회 오류입니다.'); - } - - const chatRoom: ChatRoomOfBoard = this.setChatRoomName(chatUsersOfBoard); - - return chatRoom; - } - - private setChatRoomName(chatRoom: ChatRoomOfBoard): ChatRoomOfBoard { - chatRoom.roomName = chatRoom.guestsNickname + ',' + chatRoom.hostsNickname; - - return chatRoom; - } - - private async createChatUsers( - manager: EntityManager, - chatUsers: ChatUser[], - ): Promise { - const insertResult: number = await manager - .getCustomRepository(ChatUsersRepository) - .createChatUsers(chatUsers); - - if (!insertResult) { - throw new BadRequestException('채팅방 유저정보 생성 오류입니다.'); - } - - return insertResult; - } - - private async createChatRoom( - manager: EntityManager, - chatRoom: ChatRoomBeforeCreate, - ): Promise { - const createResult: number = await manager - .getCustomRepository(ChatListRepository) - .createChatRoom(chatRoom); - if (!createResult) { - throw new InternalServerErrorException(`채팅방 생성 오류입니다.`); - } - - return createResult; - } - private async checkChatRoom( chatRoomNo: number, userNo: number, diff --git a/main-project/src/chats/chats.controller.ts b/main-project/src/chats/chats.controller.ts index 7147e204..56e4ae78 100644 --- a/main-project/src/chats/chats.controller.ts +++ b/main-project/src/chats/chats.controller.ts @@ -10,7 +10,7 @@ import { UseInterceptors, } from '@nestjs/common'; import { FilesInterceptor } from '@nestjs/platform-express'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { ApiTags } from '@nestjs/swagger'; import { ChatsControllerService } from './chats-controller.service'; import { AwsService } from 'src/aws/aws.service'; import { ChatLog } from './entity/chat-log.entity'; @@ -27,6 +27,7 @@ import { ApiGetCurrentChatLog } from './swagger/get-current-chat-log.decorator'; import { ApiInviteUser } from './swagger/invite-user.decorator'; import { ApiAcceptInvitation } from './swagger/accept-invitation.decorator'; import { APiUploadFile } from './swagger/upload-file.decorator'; +import { ApiCreateChatRoom } from './swagger/create-chat-room.decorator'; @Controller('chats') @ApiTags('채팅 APi') @@ -35,6 +36,24 @@ export class ChatsController { private readonly chatControllerService: ChatsControllerService, private readonly awsService: AwsService, ) {} + @Post('/:boardNo/:guestTeamNo') + @ApiCreateChatRoom() + @UseGuards(JwtAuthGuard) + @UseInterceptors(TransactionInterceptor) + async createChatRoom( + @GetUser() userNo: number, + @TransactionDecorator() manager: EntityManager, + @Param('boardNo', ParseIntPipe) boardNo: number, + @Param('guestTeamNo', ParseIntPipe) guestTeamNo: number, + ): Promise { + await this.chatControllerService.createChatRoom( + userNo, + manager, + boardNo, + guestTeamNo, + ); + return { msg: '여름 신청 수락' }; + } @Get('/:chatRoomNo/chat-log/:currentChatLogNo') @ApiGetPreviousChatLog() diff --git a/main-project/src/chats/chats.gateway.ts b/main-project/src/chats/chats.gateway.ts index 883b19dd..10929f49 100644 --- a/main-project/src/chats/chats.gateway.ts +++ b/main-project/src/chats/chats.gateway.ts @@ -12,10 +12,8 @@ import { Namespace, Socket } from 'socket.io'; import { WebSocketAuthGuard } from 'src/common/guards/ws-jwt-auth.guard'; import { APIResponse } from 'src/common/interface/interface'; import { ChatsGatewayService } from './chats-gateway.service'; -import { CreateChatDto } from './dto/create-chat.dto'; import { InitSocketDto } from './dto/init-socket.dto'; import { MessagePayloadDto } from './dto/message-payload.dto'; -import { ChatRoom, ChatRoomWithUsers } from './interface/chat.interface'; import { WebSocketGetUser } from 'src/common/decorator/ws-get-user.decorator'; import { WebSocketTransactionManager } from 'src/common/decorator/ws-transaction-manager.decorator'; import { WebSocketTransactionInterceptor } from 'src/common/interceptor/ws-transaction-interceptor'; @@ -78,33 +76,6 @@ export class ChatsGateway { return { response: { chatRooms } }; } - @SubscribeMessage('create-room') - @AsyncApiSub({ - description: `채팅방 생성 - response: { chatRoomNo: number } 반환`, - channel: 'create-room', - message: { - payload: CreateChatDto, - }, - }) - @UseGuards(WebSocketAuthGuard) - @UseInterceptors(WebSocketTransactionInterceptor) - async handleCreateRoom( - @WebSocketGetUser() userNo: number, - @WebSocketTransactionManager() manager: EntityManager, - @ConnectedSocket() socket: Socket, - @MessageBody() messagePayload: CreateChatDto, - ): Promise { - const chatRoom: ChatRoom = await this.chatGatewayService.createRoom( - manager, - socket, - userNo, - messagePayload, - ); - - return { response: { chatRoom } }; - } - @SubscribeMessage('message') @AsyncApiSub({ description: ` @@ -144,6 +115,7 @@ export class ChatsGateway { ); return { response: { messagePayload } }; } + @SubscribeMessage('leave-room') @UseGuards(WebSocketAuthGuard) async handleLeaveRoom( diff --git a/main-project/src/chats/chats.module.ts b/main-project/src/chats/chats.module.ts index 31674440..82b97df3 100644 --- a/main-project/src/chats/chats.module.ts +++ b/main-project/src/chats/chats.module.ts @@ -14,6 +14,7 @@ import { BoardsRepository } from 'src/boards/repository/board.repository'; import { AwsService } from 'src/aws/aws.service'; import { ChatFileUrlsRepository } from './repository/chat-file-urls.repository'; import { jwtModule } from 'src/common/configs/jwt-module.config'; +import { BoardGuestTeamsRepository } from 'src/boards/repository/board-guest-team.repository'; @Module({ imports: [ TypeOrmModule.forFeature([ @@ -26,6 +27,7 @@ import { jwtModule } from 'src/common/configs/jwt-module.config'; NoticeChatsRepository, NoticesRepository, BoardsRepository, + BoardGuestTeamsRepository, ]), jwtModule, ], diff --git a/main-project/src/chats/interface/chat.interface.ts b/main-project/src/chats/interface/chat.interface.ts index 686d1a63..e8577b1f 100644 --- a/main-project/src/chats/interface/chat.interface.ts +++ b/main-project/src/chats/interface/chat.interface.ts @@ -41,3 +41,7 @@ export interface FileUrl { chatLogNo: number; fileUrl: string; } + +export interface BoardGuestTeam { + teamNo: number; +} diff --git a/main-project/src/chats/swagger/create-chat-room.decorator.ts b/main-project/src/chats/swagger/create-chat-room.decorator.ts new file mode 100644 index 00000000..d7ef29a4 --- /dev/null +++ b/main-project/src/chats/swagger/create-chat-room.decorator.ts @@ -0,0 +1,53 @@ +import { applyDecorators } from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiNotFoundResponse, + ApiOkResponse, + ApiOperation, +} from '@nestjs/swagger'; +import { SwaggerApiResponse } from 'src/common/swagger/api-response.swagger'; + +export function ApiCreateChatRoom() { + return applyDecorators( + ApiOperation({ + summary: '채팅방 생성(여름 신청 수락)', + description: '여름 신청 받은 게스트 글을 수락 시 채팅방 생성', + }), + ApiBearerAuth(), + ApiOkResponse( + SwaggerApiResponse.success( + '채팅방 생성, 해당 유저에게 알람 전송', + '여름 요청 수락', + ), + ), + ApiNotFoundResponse( + SwaggerApiResponse.exception([ + { + name: 'boardNotFound', + example: { msg: '게시물을 찾지 못했습니다.' }, + }, + { + name: 'guestTeamNotFound', + example: { msg: '여름 요청이 존재하지 않습니다.' }, + }, + { + name: 'unmatchedTeamNo', + example: { msg: '일치하는 여름 요청이 없습니다.' }, + }, + { + name: 'alreadyExistChatRoom', + example: { msg: '이미 생성된 채팅방 입니다.' }, + }, + ]), + ), + ApiBadRequestResponse( + SwaggerApiResponse.exception([ + { + name: 'differentBoardUserNo', + example: { msg: '게시글의 작성자만 수락할 수 있습니다.' }, + }, + ]), + ), + ); +} diff --git a/main-project/src/common/configs/notice-type.config.ts b/main-project/src/common/configs/notice-type.config.ts index 13f4d932..7491cb37 100644 --- a/main-project/src/common/configs/notice-type.config.ts +++ b/main-project/src/common/configs/notice-type.config.ts @@ -10,4 +10,5 @@ export const NoticeType = { HOST_REQUEST_ALL_ACCEPTED: 9, GUEST_REQUEST_REJECTED: 10, MANNER_REQUEST: 11, + CHAT_ROOM_CREATED: 12, };