Skip to content

Commit

Permalink
[BE] 소켓 서버 관련 가드 설정 및 API 구현 (#158)
Browse files Browse the repository at this point in the history
* feat: redis exists 메서드 생성

* refactor: show quiz 책임 분리

* feat: 클래스가 비어있을 경우 빈 배열 반환

* feat: 퀴즈 다음 순서로 이동할 수 있게 추가

* feat: get class 오름차순 정렬 반환

* feat: 게임 상태 랭킹, 리더보드 추가

* feat: 실시간 소켓 서버 sid 가드 구현

* feat: 게이트웨이 가드 설정

---------

Co-authored-by: nowChae <[email protected]>
  • Loading branch information
glaxyt and nowChae authored Dec 3, 2024
1 parent aa3ae79 commit 3d864c3
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 35 deletions.
4 changes: 4 additions & 0 deletions packages/server/src/config/database/redis/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class RedisService {
}
}

async exists(key: string) {
return await this.redis.exists(key);
}

async get(key: string) {
return await this.redis.get(key);
}
Expand Down
49 changes: 21 additions & 28 deletions packages/server/src/module/game/games/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Server, Socket } from 'socket.io';
import { RedisService } from '../../../config/database/redis/redis.service';
import { v4 as uuidv4 } from 'uuid';
import { GameService } from './game.service';
import { Injectable } from '@nestjs/common';
import { Injectable, UseGuards } from '@nestjs/common';
import { MasterEntryRequestDto } from './dto/request/master-entry.request.dto';
import { ParticipantEntryRequestDto } from './dto/request/participant-entry.request.dto';
import { ShowQuizRequestDto } from './dto/request/show-quiz.request.dto';
Expand All @@ -29,6 +29,7 @@ import {
import { CONVERT_TO_MS } from '@shared/constants/utils.constants';
import { CONNECTION_TYPES } from '@shared/types/connection.types';
import { GAMESTATUS_TYPES } from '@shared/types/gameStatus.types';
import { SessionGuard } from '../../guards/session.guard';

@Injectable()
@WebSocketGateway({
Expand Down Expand Up @@ -145,12 +146,14 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return participantSid;
}

@UseGuards(SessionGuard)
@SubscribeMessage('participant notice')
async handleParticipantNotice(client: Socket, dto) {
const { pinCode } = dto;
client.to(pinCode).emit('participant notice');
}

@UseGuards(SessionGuard)
@SubscribeMessage('participant info')
async handleNickname(client: Socket, dto) {
const { pinCode, sid } = dto;
Expand All @@ -167,6 +170,8 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return { myPosition, participantList };
}


@UseGuards(SessionGuard)
@SubscribeMessage('start quiz')
async handleStartQuiz(client: Socket, payload: StartQuizRequestDto) {
const { sid, pinCode } = payload;
Expand Down Expand Up @@ -196,7 +201,6 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
const currentQuizData = quizData[updatedCurrentOrder];

const choicesLength = currentQuizData['choices'].length;

const choiceStatus = Object.fromEntries(
Array.from({ length: choicesLength }, (_, i) => [i, 0]),
);
Expand All @@ -222,13 +226,8 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return { isStarted: true };
}

// @SubscribeMessage('time end')
// async handleTimeEnd(client: Socket, payload: any) {
// const { pinCode } = payload;
// const gameInfo = JSON.parse(await this.redisService.get(`gameId=${pinCode}`));
// await this.redisService.set(`gameId=${pinCode}`, JSON.stringify(gameInfo));
// }

@UseGuards(SessionGuard)
@SubscribeMessage('show quiz')
async handleShowQuiz(client: Socket, payload: ShowQuizRequestDto) {
const { pinCode } = payload;
Expand All @@ -245,35 +244,15 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {

const isLast = gameInfo.currentOrder === quizMaxNum ? true : false;

console.log('show quiz before', client.id, currentOrder);
// 기존에 퀴즈가 저장된적이 있는지. start quiz에 저장되어있음
const quizRedis = JSON.parse(
await this.redisService.get(`gameId=${pinCode}:quizId=${currentOrder}`),
);
console.log('show quiz', client.id, quizRedis);

const startTime = quizRedis.startTime;
return { quizMaxNum, currentQuizData, startTime, isLast };
// await this.intervalTimeSender(pinCode, startTime, currentTimeLimit);
}

// async intervalTimeSender(pinCode: string, startTime: number, timeLimit: number) {
// const intervalId = setInterval(async () => {
// const currentTime = Date.now();
// const elapsedTime = currentTime - startTime;
// const remainingTime = (timeLimit + QUIZ_WAITING_TIME) * 1000 - elapsedTime;
// if (remainingTime <= 0) {
// const gameInfo = JSON.parse(await this.redisService.get(`gameId=${pinCode}`));

// gameInfo.currentOrder += 1;
// await this.redisService.set(`gameId=${pinCode}`, JSON.stringify(gameInfo));
// this.server.to(pinCode).emit('time end', { isEnd: true });
// clearInterval(intervalId);
// return;
// }
// this.server.to(pinCode).emit('timer tick', { currentTime, elapsedTime, remainingTime });
// }, INTERVAL_TIME);
// }

private async storeQuizToRedis(classId: number) {
const cachedQuizData = await this.redisService.get(`classId=${classId}`);
Expand All @@ -290,6 +269,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return quizData;
}

@UseGuards(SessionGuard)
@SubscribeMessage('submit answer')
async handleSubmitAnswer(client: Socket, payload: SubmitAnswerRequestDto) {
const { pinCode, sid, selectedAnswer, submitTime } = payload;
Expand Down Expand Up @@ -363,6 +343,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return { submitOrder: totalSubmit };
}

@UseGuards(SessionGuard)
@SubscribeMessage('emoji')
async handleEmoji(client: Socket, payload: EmojiRequestDto) {
const { pinCode, currentOrder, emoji } = payload;
Expand All @@ -386,6 +367,15 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return 0;
}

@UseGuards(SessionGuard)
@SubscribeMessage('time end')
async handleTimeEnd(client: Socket, payload: any) {
const { pinCode } = payload;
const gameInfo = JSON.parse(await this.redisService.get(`gameId=${pinCode}`));
await this.redisService.set(`gameId=${pinCode}`, JSON.stringify(gameInfo));
}

@UseGuards(SessionGuard)
@SubscribeMessage('show ranking')
async handleShowRanking(client: Socket, payload: ShowRankingRequestDto) {
const { pinCode, sid } = payload;
Expand Down Expand Up @@ -413,6 +403,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return showRankingData;
}

@UseGuards(SessionGuard)
@SubscribeMessage('end quiz')
async handleEndQuiz(client: Socket, payload: EndQuizRequestDto) {
const { sid, pinCode } = payload;
Expand Down Expand Up @@ -444,6 +435,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return equals(selectedAnswer, correctAnswers);
}

@UseGuards(SessionGuard)
@SubscribeMessage('leaderboard')
async handleLeaderboard(client: Socket, payload: LeaderboardRequestDto) {
const { pinCode } = payload;
Expand Down Expand Up @@ -472,6 +464,7 @@ export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
return leaderboardData;
}

@UseGuards(SessionGuard)
@SubscribeMessage('message')
async handleMessage(client: Socket, payload: MessageRequestDto) {
const { pinCode, message, position } = payload;
Expand Down
29 changes: 29 additions & 0 deletions packages/server/src/module/guards/session.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { RedisService } from '../../config/database/redis/redis.service';
import { WsException } from '@nestjs/websockets';

@Injectable()
export class SessionGuard implements CanActivate {
constructor(private redisService: RedisService) {}

async canActivate(context: ExecutionContext) {
const client = context.switchToWs().getClient();

const { sid } = client.handshake.auth;

if (!sid) {
client.emit('error', { message: 'Session ID not exists in Cookie.' });
return false;
}

if (
(await this.redisService.exists(`master_sid=${sid}`)) ||
(await this.redisService.exists(`participant_sid=${sid}`))
) {
client.emit('error', { message: 'Session ID not exists in Redis.' });
}

return true;
}
}
4 changes: 0 additions & 4 deletions packages/server/src/module/quiz/quizzes/quiz.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ export class QuizService {
async getAllClasses(): Promise<GetClassResponseDto[]> {
const classEntities = await this.classRepository.findAll();

if (!classEntities || classEntities.length === 0) {
throw new NotFoundException(`No classes found`);
}

return classEntities.map((classEntity: Class) => GetClassResponseDto.fromEntity(classEntity));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,15 @@ export class ClassRepository {
choices: true,
},
},
order: {
quizzes: {
position: 'ASC',
choices: {
position: 'ASC',
},
},
},
});
if (!result) {
throw new NotFoundException(`No classes found`);
}
return result;
} catch (error) {
if (error instanceof NotFoundException) throw error;
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/types/gameStatus.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export const GAMESTATUS_TYPES = {
WAITING: 'WAITING',
IN_PROGRESS: 'IN PROGRESS',
RANKING: 'RANKING',
LEADERBORAD: 'LEADERBORAD',
END: 'END',
} as const;

Expand Down

0 comments on commit 3d864c3

Please sign in to comment.