지난 시간 투표시 후보 안건을 생성 및 삭제하는 기능을 만들었습니다.
이번에는 start_poll 이벤트를 만들겠습니다.
또한 각 참가자가 순위를 제출하는 순서대로 전송할 submit_rankings 이벤트를 추가하겠습니다.
우선 기능 추가에 앞서서 필요한 타입을 추가하겠습니다. poll-types모듈에 추가 명세 및 업데이트 하도록 하겠습니다.
type NominationID = string;
export type Nominations = {
[nominationID: NominationID]: Nomination;
}
export type Rankings = {
[userID: string]: NominationID[];
};
export type Poll = {
id: string;
topic: string;
votesPerVoter: number;
participants: Participants;
nominations: Nominations;
rankings: Rankings;
// results: Results;
adminID: string;
hasStarted: boolean;
}
Poll
type 변경에 따라 PollsRepository의 변경 되어야 할 사항이 있습니다.
intialPoll
객체에 rankings key와 value는 empty object로 할당해줍니다. const initialPoll = {
id: pollID,
topic,
votesPerVoter,
participants: {},
nominations: {},
rankings: {}, // add this
adminID: userID,
hasStarted: false,
};
관리자만 사용 가능한 start_poll
기능을 추가하겠습니다.
/**
*
* Starts a poll by setting the hasStarted property to true in Redis.
* @param {string} pollID - The ID of the poll to start.
* @returns {Promise<Poll>} - A Promise that resolves with the updated Poll object.
* @throws {InternalServerErrorException} - If there was an error starting the poll.
*/
async startPoll(pollID) {
this.logger.log(`setting hasStarted for poll: ${pollID}`);
const key = `polls:${pollID}`;
try {
await this.redisClient.send_command(
'JSON.SET',
key,
'.hasStarted',
JSON.stringify(true),
);
return this.getPoll(pollID);
} catch (error) {
const errorMessage = `Failed set hasStarted for poll: ${pollID}`;
this.logger.error(errorMessage, error);
throw new InternalServerErrorException(errorMessage);
}
}
위 코드는 Redis에서 hasStarted 속성을 true로 설정하여 투표를 시작하는 비동기 메서드입니다. pollID를 매개변수로 받고, 해당 ID를 가진 투표에 대한 Redis 키를 생성합니다. 그 후 JSON.SET 명령을 사용하여 Redis에서 hasStarted 속성을 설정하고, 업데이트된 투표를 가져와 Promise로 반환합니다. 만약 에러가 발생하면, 에러 메시지를 로깅하고 InternalServerErrorException 예외를 던집니다.
async startPoll(pollID: string): Promise<Poll> {
return this.pollsRepository.startPoll(pollID);
}
매개변수로 주어진 pollID로 pollsRepository 객체의 startPoll 메서드를 호출하여 투표를 시작하는 비동기 메서드입니다. 메서드는 업데이트된 투표 객체를 반환하는 Promise를 리턴합니다.
해당 코드는 웹소켓 이벤트 'start_vote'를 처리하는 메서드로, GatewayAdminGuard 가드를 적용하여 관리자만 투표를 시작할 수 있도록 보호합니다. 이벤트 핸들러는 ConnectedSocket 데코레이터로 client 파라미터를 전달받으며, 연결된 클라이언트의 pollID를 기반으로 pollsService 객체의 startPoll 메서드를 호출하여 투표를 시작합니다. 투표가 성공적으로 시작되면 업데이트된 투표 객체를 반환하고, 해당 객체를 'poll_updated' 이벤트로 연결된 모든 클라이언트에게 emit하여 업데이트된 투표 정보를 전달합니다.
@UseGuards(GatewayAdminGuard)
@SubscribeMessage('start_vote')
async startVote(@ConnectedSocket() client: SocketWithAuth): Promise<void> {
this.logger.debug(`Attempting to start voting for poll: ${client.pollID}`);
const updatedPoll = await this.pollsService.startPoll(client.pollID);
this.io.to(client.pollID).emit('poll_updated', updatedPoll);
}
이제 순위를 제출하는 기능에 대해 작업해 보겠습니다. 실제로 서버보다 클라이언트에서 조금 더 복잡합니다.
먼저, types.ts에서 리포지토리 메서드에 대한 페이로드 타입을 정의해야 합니다.
export type AddParticipantRankingsData = {
pollID: string;
userID: string;
rankings: string[];
};
이 AddParticipantRankingsData
타입은 "pollID", "userID", "rankings"라는 세 가지 속성을 포함합니다.
/**
*
* Starts a poll by setting the hasStarted property to true in Redis.
* @param {string} pollID - The ID of the poll to start.
* @returns {Promise<Poll>} - A Promise that resolves with the updated Poll object.
* @throws {InternalServerErrorException} - If there was an error starting the poll.
*/
async startPoll(pollID: string): Promise<Poll> {
// logs a message to indicate that the hasStarted property is being set for the poll identified by pollID
this.logger.log(`setting hasStarted for poll: ${pollID}`);
// constructs the key for the poll identified by pollID in Redis
const key = `polls:${pollID}`;
try {
// sets the hasStarted property for the poll identified by pollID
await this.redisClient.send_command(
'JSON.SET',
key,
'.hasStarted',
JSON.stringify(true),
);
// retrieves the updated poll from Redis and returns it as a Promise
return this.getPoll(pollID);
} catch (error) {
// logs an error message indicating that there was an error setting the hasStarted property for the poll identified by pollID
const errorMessage = `Failed set hasStarted for poll: ${pollID}`;
this.logger.error(errorMessage, error);
// throws an `InternalServerErrorException` with a message indicating that there was an error starting the poll
throw new InternalServerErrorException(errorMessage);
}
}
위 코드는 JSDoc 형태로 작성된 함수 addParticipantRankings를 구현한 코드입니다. 이 함수는 AddParticipantRankingsData 타입을 인자로 받아서 Redis에 저장된 투표 데이터에 참가자의 랭킹을 추가하는 기능을 수행합니다. 함수의 반환값은 업데이트된 투표 객체(Poll)입니다. 만약 랭킹을 추가하는 과정에서 오류가 발생하면 InternalServerErrorException이 발생합니다.
export type SubmitRankingsFields = {
pollID: string;
userID: string;
rankings: string[];
};
순위를 제출할 수 있도록 투표가 시작되었는지 확인하기를 원하기 때문에 서비스 방법에는 실제로 약간의 애플리케이션 로직이 있습니다. 아마도 이것은 클라이언트 응용 프로그램이 이 작업을 수행하지 않기 때문에 과도한 작업일 수 있습니다. 하지만 어쨌든 할거야!
types.ts의 리포지토리에 대해 수행한 것처럼 메서드에 대한 페이로드 유형을 추가해야 합니다.
async submitRankings(rankingsData: SubmitRankingsFields): Promise<Poll> {
const hasPollStarted = this.pollsRepository.getPoll(rankingsData.pollID);
if (!hasPollStarted) {
throw new BadRequestException(
'Participants cannot rank until the poll has started.',
);
}
return this.pollsRepository.addParticipantRankings(rankingsData);
}
이 코드는 참가자들이 투표의 순위를 제출하는 기능을 수행합니다. 먼저, 제출하려는 투표가 이미 시작되었는지 확인하고, 시작되지 않았다면 예외를 발생시킵니다. 그리고 참가자가 제출한 순위를 추가하여 업데이트된 투표 객체를 반환합니다.
/**
* Submits participant rankings for a poll.
* @async
* @function submitRankings
* @param {SocketWithAuth} client - The connected client socket with authorization.
* @param {string[]} rankings - The rankings being submitted by the participant.
* @returns {Promise<void>} - A Promise that resolves once the rankings have been submitted.
* @throws {BadRequestException} - If the poll has not started yet and participant rankings cannot be submitted.
*/
@SubscribeMessage('submit_rankings')
async submitRankings(
@ConnectedSocket() client: SocketWithAuth,
@MessageBody('rankings') rankings: string[],
): Promise<void> {
// Logs the submission of rankings by the participant.
this.logger.debug(
`Submitting votes for user: ${client.userID} belonging to pollID: "${client.pollID}"`,
);
// Submits the participant's rankings to the poll.
const updatedPoll = await this.pollsService.submitRankings({
pollID: client.pollID,
userID: client.userID,
rankings,
});
// Sends the updated poll object to all clients in the poll.
this.io.to(client.pollID).emit('poll_updated', updatedPoll);
}
submit_rankings
이벤트 날릴 경우, poll 객체의 hasStarted 키가 false일때start_vote
이벤트에 빈 객체를 메시지로 날릴 경우 상태가 변경됨.submit_rankings
로 이벤트를 날릴 경우 정상적으로 처리 되는 것을 확인 할 수 있습니다.