diff --git a/client/public/index.html b/client/public/index.html index b27644df..5029fb36 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -8,7 +8,4 @@
- - - diff --git a/client/src/App.tsx b/client/src/App.tsx index 65734725..e418890f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,10 +1,11 @@ +import './styles/fonts.css'; import { useCallback, useEffect } from 'react'; import { BrowserRouter, Route, Redirect, Switch } from 'react-router-dom'; import GlobalStyles from './styles/GlobalStyles'; import Introduction from '@pages/Introduction'; import Main from '@pages/Main'; import { useUser, useUserFns } from '@contexts/userContext'; -import { getUserByToken } from '@utils/apis'; +import { getUserByToken } from '@src/apis'; import Room from '@pages/Room'; const App = (): JSX.Element => { diff --git a/client/src/utils/apis.ts b/client/src/apis.ts similarity index 89% rename from client/src/utils/apis.ts rename to client/src/apis.ts index fd257c70..1afcfa17 100644 --- a/client/src/utils/apis.ts +++ b/client/src/apis.ts @@ -1,6 +1,6 @@ import { UserProps } from '@contexts/userContext'; import { RoomInfo } from '@components/main/RoomList'; -import { HTTPResponse, queryObjToString, fetchGet, fetchPost, fetchDelete } from './apiUtils'; +import { HTTPResponse, queryObjToString, fetchGet, fetchPost, fetchDelete } from './utils/apiUtils'; interface PostLogin { email: string; @@ -12,6 +12,11 @@ export const postLogin = async (body: PostLogin): Promise> => { + const response = await fetchPost('/api/auth/logout'); + return response; +}; + interface PostJoin extends PostLogin { nickname: string; } diff --git a/client/src/components/JoinForm.tsx b/client/src/components/JoinForm.tsx index 4b6cd1e7..83f17b65 100644 --- a/client/src/components/JoinForm.tsx +++ b/client/src/components/JoinForm.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import styled, { css } from 'styled-components'; import useInput from '@hooks/useInput'; -import { postJoin } from '@utils/apis'; +import { postJoin } from '@src/apis'; import { FaGithub } from 'react-icons/fa'; import InfoMessage from './InfoMessage'; import Select from './common/Select'; diff --git a/client/src/components/LoginForm.tsx b/client/src/components/LoginForm.tsx index d690f2fc..07978aad 100644 --- a/client/src/components/LoginForm.tsx +++ b/client/src/components/LoginForm.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import styled, { css } from 'styled-components'; import useInput from '@hooks/useInput'; -import { postLogin } from '@utils/apis'; +import { postLogin } from '@src/apis'; import { useUserFns } from '@contexts/userContext'; import { FaGithub } from 'react-icons/fa'; import InfoMessage from './InfoMessage'; diff --git a/client/src/components/VideoBox.tsx b/client/src/components/VideoBox.tsx index 0a5c9a83..b6ad5885 100644 --- a/client/src/components/VideoBox.tsx +++ b/client/src/components/VideoBox.tsx @@ -7,7 +7,7 @@ import { } from 'agora-rtc-react'; import styled from 'styled-components'; import defaultImage from '@assets/default-avatar.jpeg'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef, useCallback } from 'react'; const VIDEO_WIDTH = 30; const VIDEO_HEIGHT = 20; @@ -31,11 +31,11 @@ const VolumeVisualizer = styled.div` height: ${VIDEO_HEIGHT}rem; position: absolute; right: 0; - border: 3px solid ${({ theme }) => theme.colors.primary}; + border: 3px solid ${({ theme }) => theme.colors.blue}; border-radius: ${BORDER_RADIUS}; `; -const SPEAK_VOLUME = 0.1; +const SPEAK_VOLUME = 0.2; const VOLUME_VISUAL_TIME = 1000; interface VideoBoxProps { @@ -45,13 +45,27 @@ interface VideoBoxProps { const VideoBox = ({ videoTrack, audioTrack }: VideoBoxProps): JSX.Element => { const [isSpeak, setIsSpeak] = useState(false); + const isInterval = useRef(false); + + const initInterval = useCallback( + function () { + if (!isInterval.current) { + setInterval(() => { + if (audioTrack) { + setIsSpeak(audioTrack?.getVolumeLevel() > SPEAK_VOLUME); + } + }, VOLUME_VISUAL_TIME); + } + isInterval.current = true; + }, + [isInterval, audioTrack], + ); useEffect(() => { - if (audioTrack) - setInterval(() => { - setIsSpeak(audioTrack?.getVolumeLevel() > SPEAK_VOLUME); - }, VOLUME_VISUAL_TIME); - }, [audioTrack]); + if (audioTrack) { + initInterval(); + } + }, [audioTrack, initInterval]); return ( diff --git a/client/src/components/main/CreateForm.tsx b/client/src/components/main/CreateForm.tsx index 767ae9a0..f74b28ae 100644 --- a/client/src/components/main/CreateForm.tsx +++ b/client/src/components/main/CreateForm.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useHistory } from 'react-router-dom'; import styled, { css } from 'styled-components'; import useInput from '@hooks/useInput'; -import { postRoom } from '@utils/apis'; +import { postRoom } from '@src/apis'; import { useUser } from '@contexts/userContext'; import Select from '@components/common/Select'; import { adminOptions } from '@utils/utils'; diff --git a/client/src/components/main/RoomList.tsx b/client/src/components/main/RoomList.tsx index 6ecbb929..b7909894 100644 --- a/client/src/components/main/RoomList.tsx +++ b/client/src/components/main/RoomList.tsx @@ -6,8 +6,7 @@ import RoomBox from '@components/RoomBox'; import Tab from '@components/common/Tab'; import { UserProps } from '@src/contexts/userContext'; - -import { getRoom } from '@utils/apis'; +import { getRoom } from '@src/apis'; const RoomListGrid = styled.div` padding: ${({ theme }) => theme.paddings.lg} 0; diff --git a/client/src/components/main/SideBar.tsx b/client/src/components/main/SideBar.tsx index 83012761..737fdc92 100644 --- a/client/src/components/main/SideBar.tsx +++ b/client/src/components/main/SideBar.tsx @@ -5,7 +5,7 @@ import { useUser, useUserFns } from '@contexts/userContext'; import { IoLogOutOutline } from 'react-icons/io5'; import Modal from '@components/common/Modal'; import CreateForm from './CreateForm'; -import { setCookie } from '@utils/cookie'; +import { postLogout } from '@src/apis'; const SIDEBAR_MIN_WIDTH = '29rem'; @@ -102,12 +102,14 @@ const LogoutBtn = styled.button` const UserAvatar = styled.img` margin-right: ${({ theme }) => theme.margins.base}; - width: 5rem; - height: 5rem; + width: 3rem; + height: 3rem; border-radius: 50%; overflow: hidden; `; +const UserNickname = styled.span``; + const SideBar = (): JSX.Element => { const [loginModal, setLoginModal] = useState(false); const [createModal, setCreateModal] = useState(false); @@ -117,9 +119,11 @@ const SideBar = (): JSX.Element => { const onClickLoginBtn = () => setLoginModal(!loginModal); const onClickCreateBtn = () => setCreateModal(true); const onClickUserInfoBtn = () => {}; - const onClickLogoutBtn = () => { - setCookie('access-token', ''); - logUserOut(); + const onClickLogoutBtn = async () => { + const { isOk } = await postLogout(); + if (isOk) { + logUserOut(); + } }; return ( @@ -129,7 +133,7 @@ const SideBar = (): JSX.Element => { <> - {user.nickname} + {user.nickname} 로그아웃 diff --git a/client/src/components/room/tadaktadak/VideoController.tsx b/client/src/components/room/tadaktadak/VideoController.tsx index 17043b8f..041b7657 100644 --- a/client/src/components/room/tadaktadak/VideoController.tsx +++ b/client/src/components/room/tadaktadak/VideoController.tsx @@ -1,13 +1,13 @@ import React, { useState, useCallback, useEffect, useContext } from 'react'; import { useHistory } from 'react-router'; import { ICameraVideoTrack, IMicrophoneAudioTrack } from 'agora-rtc-react'; +import styled, { css, ThemeContext } from 'styled-components'; import { FaMicrophone, FaMicrophoneSlash, FaVideo, FaVideoSlash } from 'react-icons/fa'; import { MdOutlineExitToApp, MdScreenShare, MdStopScreenShare } from 'react-icons/md'; -import styled, { css, ThemeContext } from 'styled-components'; import { useClient } from './videoConfig'; import Button from '@components/common/Button'; import ScreenShareDiv from './ScreenShareDiv'; -import { deleteRoom } from '@utils/apis'; +import { deleteRoom } from '@src/apis'; import { useUser } from '@contexts/userContext'; const ButtonContainer = styled.div` diff --git a/client/src/pages/Introduction/Introduction.tsx b/client/src/pages/Introduction/Introduction.tsx index d33a6ace..e2479afd 100644 --- a/client/src/pages/Introduction/Introduction.tsx +++ b/client/src/pages/Introduction/Introduction.tsx @@ -20,13 +20,13 @@ const IntroContainer = styled.div` `} transition: background-color 1500ms linear; padding: ${({ theme }) => theme.paddings.base}; - font-family: 'Dongle', sans-serif; + font-family: 'Dongle'; `; const IntroTitle = styled.div` z-index: 2; color: white; - font-size: 200px; + font-size: 250px; `; const IntroDescription = styled.div` z-index: 2; diff --git a/client/src/pages/Room/style.ts b/client/src/pages/Room/style.ts index 92b1b8bd..dbe28f23 100644 --- a/client/src/pages/Room/style.ts +++ b/client/src/pages/Room/style.ts @@ -8,5 +8,6 @@ export const RoomWrapper = styled.div` export const RoomContainer = styled.div` height: 100%; + width: 100%; padding: ${({ theme }) => theme.paddings.lg}; `; diff --git a/client/src/styles/GlobalStyles.ts b/client/src/styles/GlobalStyles.ts index 284c3aa6..d72574d4 100644 --- a/client/src/styles/GlobalStyles.ts +++ b/client/src/styles/GlobalStyles.ts @@ -21,7 +21,7 @@ const GlobalStyles = createGlobalStyle` font-size : 62.5%; // 1rem === 10px } body { - font-family: 'Dongle', sans-serif; + font-family: 'Dongle'; } a { text-decoration: none; diff --git a/client/src/styles/fonts.css b/client/src/styles/fonts.css new file mode 100644 index 00000000..8c9a1a48 --- /dev/null +++ b/client/src/styles/fonts.css @@ -0,0 +1,7 @@ +@font-face { + font-family: 'Dongle'; + font-style: normal; + font-weight: 700; + src: url('./fonts/dongle-v4-korean-700.woff2') format('woff2'), + url('./fonts/dongle-v4-korean-700.woff') format('woff'); +} ; diff --git a/client/src/styles/fonts/dongle-v4-korean-700.woff b/client/src/styles/fonts/dongle-v4-korean-700.woff new file mode 100644 index 00000000..733cd4e6 Binary files /dev/null and b/client/src/styles/fonts/dongle-v4-korean-700.woff differ diff --git a/client/src/styles/fonts/dongle-v4-korean-700.woff2 b/client/src/styles/fonts/dongle-v4-korean-700.woff2 new file mode 100644 index 00000000..25194922 Binary files /dev/null and b/client/src/styles/fonts/dongle-v4-korean-700.woff2 differ diff --git a/client/src/utils/apiUtils.ts b/client/src/utils/apiUtils.ts index 95a473a9..f864aed3 100644 --- a/client/src/utils/apiUtils.ts +++ b/client/src/utils/apiUtils.ts @@ -25,8 +25,9 @@ export function queryObjToString(queryObj: T): string { .join('&'); } -function getOptions(): RequestInit { +function simpleOptions(method: string): RequestInit { return { + method, headers: { Accept: 'application/json', }, @@ -34,26 +35,26 @@ function getOptions(): RequestInit { }; } +function getOptions(): RequestInit { + return simpleOptions('GET'); +} + function postOptions(body: BodyType): RequestInit { - return { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify(body), - }; + return Object.keys(body).length + ? { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify(body), + } + : simpleOptions('POST'); } function deleteOptions(): RequestInit { - return { - method: 'DELETE', - headers: { - Accept: 'application/json', - }, - credentials: 'include', - }; + return simpleOptions('DELETE'); } export async function fetcher(url: string, options: RequestInit): Promise> { @@ -89,9 +90,10 @@ export async function fetchGet(url: string, query?: string): Promise(url: string, body: BodyType): Promise> { +export async function fetchPost(url: string, body: BodyType = {}): Promise> { const requestUrl = getUrl(url); const response = await fetcher(requestUrl, postOptions(body)); + console.log(postOptions(body)); return response; } diff --git a/server/api/src/domain/auth/auth.module.ts b/server/api/src/domain/auth/auth.module.ts index 8c94a148..39ee1c3f 100644 --- a/server/api/src/domain/auth/auth.module.ts +++ b/server/api/src/domain/auth/auth.module.ts @@ -6,11 +6,12 @@ import { JwtStrategy } from './strategy/jwt.strategy'; import { AuthController } from './controller/auth.controller'; import { AuthService } from './service/auth.service'; import { UserRepository } from '../user/repository/user.repository'; +import { DevFieldRepository } from '../field/repository/dev-field.repository'; import { HistoryModule } from '../history/history.module'; @Module({ imports: [ - TypeOrmModule.forFeature([UserRepository]), + TypeOrmModule.forFeature([UserRepository, DevFieldRepository]), PassportModule, JwtModule.register({ secret: 'secretkey', diff --git a/server/api/src/domain/auth/service/auth.service.ts b/server/api/src/domain/auth/service/auth.service.ts index af08910c..33fa10af 100644 --- a/server/api/src/domain/auth/service/auth.service.ts +++ b/server/api/src/domain/auth/service/auth.service.ts @@ -4,13 +4,15 @@ import { JwtService } from '@nestjs/jwt'; import { LocalDate } from 'js-joda'; import { Bcrypt } from 'src/utils/bcrypt'; import { UserBuilder } from '../../../builder'; -import { UserException } from '../../../exception'; +import { DevFieldException, UserException } from '../../../exception'; import { User } from '../../user/user.entity'; import { HistoryService } from 'src/domain/history/service/history.service'; import { UserRepository } from '../../user/repository/user.repository'; +import { DevFieldRepository } from 'src/domain/field/repository/dev-field.repository'; import { LoginRequestDto } from '../dto/login-request.dto'; import { UserResponseDto } from '../dto/user-response.dto'; import { JoinRequestDto } from '../dto/join-request.dto'; +import { DevField } from 'src/domain/field/dev-field.entity'; @Injectable() export class AuthService { @@ -18,6 +20,9 @@ export class AuthService { @InjectRepository(UserRepository) private readonly userRepository: UserRepository, + @InjectRepository(DevFieldRepository) + private readonly devFieldRepository: DevFieldRepository, + private readonly historyService: HistoryService, private jwtService: JwtService, ) {} @@ -42,11 +47,14 @@ export class AuthService { const { nickname, email, password } = joinRequestDto; const isExistUser = await this.userRepository.exists(joinRequestDto); if (isExistUser) throw UserException.userIsExist(); + const devField: DevField = await this.devFieldRepository.findDevById(joinRequestDto.devFieldId); + if (!devField) throw DevFieldException.devFieldNotFound(); const user: User = new UserBuilder() .setNickName(nickname) .setEmail(email) .setPassword(Bcrypt.hash(password)) .setImageURL(process.env.DEFAULT_IMG) + .setDevField(devField) .build(); await this.userRepository.save(user); return true; diff --git a/server/api/src/domain/room/controller/room.controller.ts b/server/api/src/domain/room/controller/room.controller.ts index 916c25d6..97682d38 100644 --- a/server/api/src/domain/room/controller/room.controller.ts +++ b/server/api/src/domain/room/controller/room.controller.ts @@ -47,6 +47,18 @@ export class RoomController { return { result: await this.roomService.getRoomByUUID(uuid) }; } + @Post(':uuid/join') + @UseGuards(JwtAuthGuard) + async joinRoom(@Param('uuid') uuid: string): Promise<{ result: boolean }> { + return { result: await this.roomService.joinRoom(uuid) }; + } + + @Post(':uuid/leave') + @UseGuards(JwtAuthGuard) + async leaveRoom(@Param('uuid') uuid: string): Promise<{ result: boolean }> { + return { result: await this.roomService.leaveRoom(uuid) }; + } + @Delete(':uuid') @UseGuards(JwtAuthGuard) async deleteRoom(@Req() req: Request, @Param('uuid') uuid: string): Promise<{ result: boolean }> { diff --git a/server/api/src/domain/room/dto/room-response.dto.ts b/server/api/src/domain/room/dto/room-response.dto.ts index 44ee8ba5..803ba0c5 100644 --- a/server/api/src/domain/room/dto/room-response.dto.ts +++ b/server/api/src/domain/room/dto/room-response.dto.ts @@ -14,6 +14,8 @@ export class RoomResponseDto { agoraAppId: string; agoraToken: string; owner: UserResponseDto; + nowHeadcount: number; + maxHeadcount: number; constructor(room: Room) { const owner: User = room.owner; @@ -26,6 +28,8 @@ export class RoomResponseDto { this.uuid = room.uuid; this.agoraAppId = room.agoraAppId; this.agoraToken = room.agoraToken; + this.nowHeadcount = room.nowHeadcount; + this.maxHeadcount = room.maxHeadcount; this.owner = new UserResponseDtoBuilder() .setId(room.owner.id) .setNickName(owner.nickName) diff --git a/server/api/src/domain/room/repository/room.repository.ts b/server/api/src/domain/room/repository/room.repository.ts index 0740c538..b177cb27 100644 --- a/server/api/src/domain/room/repository/room.repository.ts +++ b/server/api/src/domain/room/repository/room.repository.ts @@ -1,7 +1,12 @@ -import { DeleteResult, EntityRepository, Like, Repository } from 'typeorm'; +import { DeleteResult, EntityRepository, Like, Repository, UpdateResult } from 'typeorm'; import { PaginationOptions } from '../../../paginate'; import { Room, RoomType } from '../room.entity'; +export enum RoomProcessOption { + Join = '+ 1', + Leave = '- 1', +} + @EntityRepository(Room) export class RoomRepository extends Repository { async findByKeywordAndCount(options: PaginationOptions, roomType: RoomType): Promise<[Room[], number]> { @@ -25,6 +30,16 @@ export class RoomRepository extends Repository { .getOne(); } + async roomProcess(uuid: string, option: RoomProcessOption): Promise { + return await this.createQueryBuilder() + .update(Room) + .set({ + nowHeadcount: () => `now_headcount ${option}`, + }) + .where('uuid = :uuid', { uuid }) + .execute(); + } + async findRoomByUserEmail(email: string): Promise { return this.createQueryBuilder('room') .leftJoinAndSelect('room.owner', 'user') diff --git a/server/api/src/domain/room/service/room.service.ts b/server/api/src/domain/room/service/room.service.ts index 72611091..b8a8fb00 100644 --- a/server/api/src/domain/room/service/room.service.ts +++ b/server/api/src/domain/room/service/room.service.ts @@ -3,15 +3,14 @@ import { InjectRepository } from '@nestjs/typeorm'; import { v4 as uuidv4 } from 'uuid'; import { RtcRole, RtcTokenBuilder } from 'agora-access-token'; import { RoomBuilder } from '../../../builder'; -import { UserException, RoomException } from '../../../exception'; +import { RoomException, UserException } from '../../../exception'; import { Pagination, PaginationOptions } from '../../../paginate'; -import { Connection, DeleteResult, getConnection } from 'typeorm'; +import { Connection, DeleteResult } from 'typeorm'; import { Room, RoomType } from '../room.entity'; -import { RoomRepository } from '../repository/room.repository'; +import { RoomProcessOption, RoomRepository } from '../repository/room.repository'; import { UserRepository } from '../../user/repository/user.repository'; import { CreateRoomRequestDto } from '../dto/create-room-request.dto'; import { RoomResponseDto } from '../dto/room-response.dto'; -import { find } from 'rxjs'; @Injectable() export class RoomService { @@ -29,6 +28,24 @@ export class RoomService { return new RoomResponseDto(findRoom); } + async joinRoom(uuid: string): Promise { + const findRoom = await this.roomRepository.findRoomByUUID(uuid); + if (!findRoom) throw RoomException.roomNotFound(); + if (findRoom.nowHeadcount === findRoom.maxHeadcount) throw RoomException.roomFullError(); + const updateResult = await this.roomRepository.roomProcess(uuid, RoomProcessOption.Join); + if (!updateResult) throw RoomException.roomJoinError(); + return true; + } + + async leaveRoom(uuid: string): Promise { + const findRoom = await this.roomRepository.findRoomByUUID(uuid); + if (!findRoom) throw RoomException.roomNotFound(); + if (findRoom.nowHeadcount < 1) throw RoomException.roomLeaveError(); + const updateResult = await this.roomRepository.roomProcess(uuid, RoomProcessOption.Leave); + if (!updateResult) throw RoomException.roomLeaveError(); + return true; + } + async getRoomListAll(options: PaginationOptions, roomType: RoomType): Promise> { const [results, total] = await this.roomRepository.findByKeywordAndCount(options, roomType); return new Pagination({ diff --git a/server/api/src/domain/user/repository/user.repository.ts b/server/api/src/domain/user/repository/user.repository.ts index 6c2c1c8d..c386bfdf 100644 --- a/server/api/src/domain/user/repository/user.repository.ts +++ b/server/api/src/domain/user/repository/user.repository.ts @@ -19,6 +19,7 @@ export class UserRepository extends Repository { async findUserByNickname(nickname: string): Promise { return await this.findOne({ where: { nickName: nickname } }); } + async findUserByNicknameWithDev(nickname: string): Promise { return await this.findOne({ where: { nickName: nickname }, relations: ['devField'] }); } diff --git a/server/api/src/exception/room.exception.ts b/server/api/src/exception/room.exception.ts index b464ccfb..44f84816 100644 --- a/server/api/src/exception/room.exception.ts +++ b/server/api/src/exception/room.exception.ts @@ -34,4 +34,25 @@ export class RoomException { message: '방 삭제중 오류가 발생했습니다.', }); } + + static roomJoinError(): HttpException { + return new InternalServerErrorException({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: '방 참여중 오류가 발생했습니다.', + }); + } + + static roomLeaveError(): HttpException { + return new InternalServerErrorException({ + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + message: '방 퇴장중 오류가 발생했습니다.', + }); + } + + static roomFullError(): HttpException { + return new BadRequestException({ + statusCode: HttpStatus.BAD_REQUEST, + message: '방의 참여자가 가득찼습니다.', + }); + } } diff --git a/server/socket/src/gateway/room.gateway.ts b/server/socket/src/gateway/room.gateway.ts index 731386d4..eb1f336f 100644 --- a/server/socket/src/gateway/room.gateway.ts +++ b/server/socket/src/gateway/room.gateway.ts @@ -31,28 +31,44 @@ export class RoomGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa } @SubscribeMessage('join-room') - handleJoinRoom(client: Socket, { field, img, nickname, roomId }: IRoomRequest): void { + handleJoinRoom(client: Socket, { field, img, nickname, roomId, maxHead }: IRoomRequest): void { + client.leave(client.id); //verify-room에서 생성한 룸을 삭제합니다. 이제 사용될 일이 없어서.. client.join(roomId); - if (!this.userList[roomId]) this.userList[roomId] = {}; - this.userList[roomId][nickname] = { + if (!this.userList[roomId]) this.userList[roomId] = { list: {}, maxHead: maxHead }; + this.userList[roomId]['list'][nickname] = { field: field, img: img, }; - this.server.to(roomId).emit('user-list', this.userList[roomId]); + this.server.to(roomId).emit('user-list', this.userList[roomId]['list']); } @SubscribeMessage('leave-room') handleLeaveRoom(client: Socket, { nickname, roomId }: IRoomRequest): void { client.leave(roomId); - delete this.userList[roomId][nickname]; - this.server.to(roomId).emit('user-list', this.userList[roomId]); + delete this.userList[roomId]['list'][nickname]; + this.server.to(roomId).emit('user-list', this.userList[roomId]['list']); } @SubscribeMessage('kick-room') handleKickRoom(client: Socket, { roomId, kickNickname }: IRoomRequest): void { if (!kickNickname) return; - delete this.userList[roomId][kickNickname]; - this.server.to(roomId).emit('user-list', this.userList[roomId]); + delete this.userList[roomId]['list'][kickNickname]; + this.server.to(roomId).emit('user-list', this.userList[roomId]['list']); + } + + @SubscribeMessage('verify-room') + handleVerifyRoom(client: Socket, { roomId }: IRoomRequest): void { + client.join(client.id); //해당 사용자한테만 전송될 수 있게 + if (!this.userList[roomId]) { + this.server.to(client.id).emit('is-verify', true); + return; + } + if (Object.keys(this.userList[roomId]['list']).length < this.userList[roomId]['maxHead']) { + this.server.to(client.id).emit('is-verify', true); + return; + } + + this.server.to(client.id).emit('is-verify', false); } afterInit(server: Server) { diff --git a/server/socket/src/gateway/room.interface.ts b/server/socket/src/gateway/room.interface.ts index 4c5ee3fc..6cd938f8 100644 --- a/server/socket/src/gateway/room.interface.ts +++ b/server/socket/src/gateway/room.interface.ts @@ -6,6 +6,7 @@ export interface IRoomRequest { kickNickname?: string; field?: string; img?: string; + maxHead: number; } export enum MessageType {