diff --git a/src/channels/channel-invitation.repository.ts b/src/channels/channel-invitation.repository.ts index 615f558..38f255b 100644 --- a/src/channels/channel-invitation.repository.ts +++ b/src/channels/channel-invitation.repository.ts @@ -22,5 +22,7 @@ export class ChannelInvitationRepository extends Repository { const result = await this.save(channelInvitation); if (!result) throw DBUpdateFailureException('create channel invitation failed'); + + return result; } } diff --git a/src/channels/channels.controller.ts b/src/channels/channels.controller.ts index 4b995dc..a30f496 100644 --- a/src/channels/channels.controller.ts +++ b/src/channels/channels.controller.ts @@ -3,6 +3,7 @@ import { Body, Controller, Get, + Logger, Param, ParseIntPipe, Patch, @@ -69,14 +70,6 @@ export class ChannelsController { const createChannelResponseDto = await this.channelsService.createChannel(user.id, channelInfo); - // channel room에 join - if (user.channelSocketId) { - this.channelsGateway.joinChannelRoom( - createChannelResponseDto.channelId.toString(), - user.channelSocketId, - ); - } - return createChannelResponseDto; } @@ -139,15 +132,8 @@ export class ChannelsController { const channelUsersResponseDto = await this.channelsService.createChannelUser(channelUserParamDto); - - if (user.channelSocketId) { - this.channelsGateway.joinChannelRoom( - channelId.toString(), - user.channelSocketId, - ); - } - - return channelUsersResponseDto; + + return channelUsersResponseDto ; } @Patch('/exit') @@ -168,8 +154,7 @@ export class ChannelsController { @ApiOperation({ summary: '유저 초대', description: '유저 초대' }) async createChannelInvitation( @GetUser() user: User, - @Body() - createInvitationRequestDto: CreateInvitationRequestDto, + @Body() createInvitationRequestDto: CreateInvitationRequestDto, ) { const invitingUserId = user.id; const channelId = createInvitationRequestDto.channelId; diff --git a/src/channels/channels.gateway.ts b/src/channels/channels.gateway.ts index 0b76944..d1936a5 100644 --- a/src/channels/channels.gateway.ts +++ b/src/channels/channels.gateway.ts @@ -22,6 +22,7 @@ import { UsersRepository } from 'src/users/users.repository'; import { ChannelUsersRepository } from './channel-users.repository'; import { EventMessageOnDto } from './dto/event-message-on.dto'; import { GatewayCreateInvitationParamDto } from '../game/dto/gateway-create-invitation-param.dto'; +import { CreateInvitationParamDto } from './dto/create-invitation-param.dto'; @WebSocketGateway({ namespace: 'channels' }) @UseFilters(WsExceptionFilter) @@ -51,6 +52,7 @@ export class ChannelsGateway async handleConnection(@ConnectedSocket() client: Socket, ...args: any[]) { // Socket으로부터 user 정보를 가져온다. + const user = await this.authService.getUserFromSocket(client); if (!user || !client.id || user.channelSocketId) { return client.disconnect(); @@ -129,8 +131,6 @@ export class ChannelsGateway @ConnectedSocket() client: Socket, @MessageBody() data: EventMessageOnDto, ) { - this.logger.log(`handleMessage: ${JSON.stringify(data)}`); // TODO: test code. 추후 삭제 - // Socket으로부터 user 정보를 가져온다. const user = await this.authService.getUserFromSocket(client); @@ -138,7 +138,7 @@ export class ChannelsGateway throw WSBadRequestException('유저 정보가 일치하지 않습니다.'); // TODO: exception 발생해도 서버 죽지 않는지 확인 } - const { channelId, message } = data; + const { channelId, message, notice } = data; // 채널유저 유효성 검사 const channelUser = await this.channelUsersRepository.findOne({ @@ -151,18 +151,18 @@ export class ChannelsGateway const eventMessageEmitDto = { nickname: user.nickname, message, + channelId, + notice, }; // mute된 유저의 socketId List를 가져온다. const muteRedisKey = `mute:${channelId}:${user.id}`; - // console.log('muteRedisKey:', muteRedisKey); const isMutedChannelUser = await this.redis.exists(muteRedisKey); if (isMutedChannelUser) { throw WSBadRequestException('채널에서 음소거 되었습니다.'); } - // console.log('muteRedisKey:', muteRedisKey); - // 채널에 메시지를 emit한다. + // 어떤 user가 보내는지 찾아주기 위해 emit한다. (nickname, message, channelId, notice) this.server .to(channelId.toString()) .emit('message', eventMessageEmitDto); @@ -177,25 +177,13 @@ export class ChannelsGateway const socket = (await this.server.fetchSockets()).find( (s) => s.id === channelSocketId, ); - if (!socket) { return WSBadRequestException('socket이 존재하지 않습니다.'); } - this.logger.log(`socket.id: `, socket.id); socket.join(channelRoomName); } - @SubscribeMessage('ClientToServer') // TODO: test용 코드. 추후 삭제 - handleMessageTest( - @ConnectedSocket() client: Socket, - @MessageBody() data: string, - ) { - this.logger.log(`client TEST: `, client); - this.logger.log('ClientToServer: ', data); - this.server.emit('ServerToClient', 'Hello Client!'); - } - async inviteGame( gatewayInvitationParamDto: GatewayCreateInvitationParamDto, ) { @@ -230,4 +218,51 @@ export class ChannelsGateway gameType: gatewayInvitationParamDto.gameType, }); } + + //소켓 연결 해제 시, 채널 룸에서 leave하는 메서드 + @SubscribeMessage('leaveChannelRoom') + async handleleaveChannelRoom( + @ConnectedSocket() client: Socket, + @MessageBody() data: { channelId: number }, + ) { + const user = await this.authService.getUserFromSocket(client); + if (!user || !client.id || user.channelSocketId !== client.id) { + return WSBadRequestException('유저 정보가 일치하지 않습니다.'); + } + + const { channelId } = data; + + // 채널유저 유효성 검사 + const channelUser = await this.channelUsersRepository.findOne({ + where: { userId: user.id, channelId, isBanned: false }, + }); + if (!channelUser) { + throw WSBadRequestException('채널에 속해있지 않습니다.'); + } + // 채널에 leave한다. + client.leave(channelId.toString()); + + this.server + .to(channelId.toString()) + .emit('message', `${user.nickname}님이 퇴장하셨습니다.`); + + // 어느 채널에 퇴장했는지 알려주기 위해 front에 emit한다. + client + .emit('leaveChannelRoom', channelId.toString()); + } + + // 알람 구현을 위한 메소드(한명에게만 알람) + async PrivateAlert( + data: { invitedUserId: number, invitationId: number } + ) { + const invitedUser = await this.usersRepository.findOne({ + where: { id: data.invitedUserId }, + }); + const invitationId = data.invitationId; + + if (!invitedUser || invitedUser.status === UserStatus.OFFLINE || !invitedUser.channelSocketId) { + throw WSBadRequestException('유저가 유효하지 않습니다.'); + } + this.server.to(invitedUser.channelSocketId).emit('privateAlert', invitationId); + } } diff --git a/src/channels/channels.service.ts b/src/channels/channels.service.ts index 7b7ea6f..58dc12f 100644 --- a/src/channels/channels.service.ts +++ b/src/channels/channels.service.ts @@ -1,3 +1,4 @@ +import { ChannelsGateway } from './channels.gateway'; import { InjectRedis } from '@liaoliaots/nestjs-redis'; import { BadRequestException, Injectable, Logger } from '@nestjs/common'; import * as bycrypt from 'bcrypt'; @@ -20,6 +21,7 @@ import { CreateInvitationParamDto } from './dto/create-invitation-param.dto'; import { DmChannelListResponseDto } from './dto/dmchannel-list-response.dto'; import { DmChannelListReturnDto } from './dto/dmchannel-list-return.dto'; import { UpdateChannelPwdParamDto } from './dto/update-channel-pwd-param.dto'; +import { GatewayCreateChannelInvitationParamDto } from 'src/game/dto/gateway-create-channelInvitation-param-dto'; @Injectable() export class ChannelsService { @@ -28,6 +30,7 @@ export class ChannelsService { private readonly channelUsersRepository: ChannelUsersRepository, private readonly channelInvitationRepository: ChannelInvitationRepository, private readonly usersRepository: UsersRepository, + private readonly ChannelsGateway: ChannelsGateway, @InjectRedis() private readonly redis: Redis, ) {} @@ -99,8 +102,20 @@ export class ChannelsService { userId, channel.id, ); + // // 채널에 소환된 유저에게 알람 전송. DM의 경우에만 해당 + // if (channel.channelType === ChannelType.DM) { + // console.log(`channelInfo.userId: ${channelInfo.userId}`) + // const targetUser = await this.usersRepository.findOne({ + // where: { id: channelInfo.userId }, + // }); + // if (targetUser?.channelSocketId) { + // this.ChannelsGateway.sendChannelAlert( + // channel.id, + // [targetUser.channelSocketId], + // ); + // } + // } - // TODO: 채널에 소환된 유저에게 알림 전송. DM의 경우에만 해당 // TODO: cache에 user count 저장 this.logger.log( `channel ${channel.id} is created. user count: ${userCount}`, @@ -142,7 +157,6 @@ export class ChannelsService { userId, channelId, ); - return { channelUsers: channelUserInfoList, myChannelUserType: myChannelUserInfo.channelUserType, @@ -289,11 +303,15 @@ export class ChannelsService { ); } - await this.channelInvitationRepository.createChannelInvitation( + const channelInvitation = await this.channelInvitationRepository.createChannelInvitation( createInvitationParamDto, ); - // TODO: 알림 보내기 + const gatewayInvitationParamDto: GatewayCreateChannelInvitationParamDto = { + invitationId: channelInvitation.id, + invitedUserId: invitedUserId, + }; + await this.ChannelsGateway.PrivateAlert(gatewayInvitationParamDto); } async updateChannelUser(userId: number, channelId: number) { diff --git a/src/game/dto/gateway-create-channelInvitation-param-dto.ts b/src/game/dto/gateway-create-channelInvitation-param-dto.ts new file mode 100644 index 0000000..300bf8b --- /dev/null +++ b/src/game/dto/gateway-create-channelInvitation-param-dto.ts @@ -0,0 +1,5 @@ +export class GatewayCreateChannelInvitationParamDto { + invitationId: number; + + invitedUserId: number; +}