Skip to content

Commit

Permalink
Merge pull request #116 from modu-menu/feat/add-finish-vote
Browse files Browse the repository at this point in the history
투표 종료 API 구현
  • Loading branch information
eelseungmin authored Jun 5, 2024
2 parents 34fde45 + fcac185 commit 65b63bd
Show file tree
Hide file tree
Showing 8 changed files with 356 additions and 4 deletions.
2 changes: 0 additions & 2 deletions src/main/java/modu/menu/core/exception/Exception400.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import lombok.Getter;
import modu.menu.core.response.ApiFailResponse;
import modu.menu.core.response.ApiSuccessResponse;
import modu.menu.core.response.ErrorData;
import org.springframework.http.HttpStatus;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public ResponseEntity<ApiFailResponse> handleConstraintViolationException(Constr

@ExceptionHandler(Exception400.class)
public ResponseEntity<ApiFailResponse> badRequest(Exception400 e) {
log.warn("Exception: 400, " + e.getMessage());
log.warn("Exception: 400, " + e.getValue());
return ResponseEntity
.status(e.status())
.body(e.body());
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/modu/menu/core/response/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ public enum ErrorMessage {
// Vote
NOT_EXIST_VOTE("해당 ID와 일치하는 투표가 존재하지 않습니다."),
CANT_INVITE_TO_END_VOTE("종료된 투표에는 초대할 수 없습니다."),
NOT_EXIST_PLACE("해당 ID와 일치하는 음식점이 존재하지 않습니다.");
NOT_EXIST_PLACE("해당 ID와 일치하는 음식점이 존재하지 않습니다."),
CANT_FINISH_ALREADY_END_VOTE("이미 종료된 투표입니다."),
NOT_ALLOWED_USER("투표에 초대된 회원이 아닙니다."),
CANT_FINISH_BY_PARTICIPANT("주최자만 투표를 종료할 수 있습니다.");

// domainEx1

Expand Down
21 changes: 21 additions & 0 deletions src/main/java/modu/menu/vote/api/VoteController.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,27 @@ public ResponseEntity<ApiSuccessResponse> invite(
.body(new ApiSuccessResponse<>());
}

// 투표 종료
@Operation(summary = "투표 종료", description = "투표를 종료합니다.")
@SecurityRequirement(name = "Authorization")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "투표 종료가 성공한 경우"),
@ApiResponse(responseCode = "400", description = "PathVariable이 형식에 맞지 않거나 투표가 이미 종료된 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "401", description = "토큰 인증이 실패한 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "403", description = "투표에 권한이 없는 회원인 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "404", description = "조회하려는 투표 자체가 존재하지 않는 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
@ApiResponse(responseCode = "500", description = "그 외 서버에서 처리하지 못한 에러가 발생했을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class)))
})
@PatchMapping("/api/vote/{voteId}/status")
public ResponseEntity<ApiSuccessResponse> finishVote(
@Positive(message = "voteId는 양수여야 합니다.") @PathVariable("voteId") Long voteId
) {
voteService.finishVote(voteId);

return ResponseEntity.ok()
.body(new ApiSuccessResponse<>());
}

/**
* 회원의 위도와 경도를 외부에 노출해선 안 된다고 판단하여
* HTTPS로 통신하는 점을 이용해 RequestBody에 위치 데이터를 담아서 요청하도록 설계
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/modu/menu/vote/domain/Vote.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,8 @@ public void removeReview(Review review) {
review.syncVote(null);
reviews.remove(review);
}

public void updateVoteStatus(VoteStatus voteStatus) {
this.voteStatus = voteStatus;
}
}
26 changes: 26 additions & 0 deletions src/main/java/modu/menu/vote/service/VoteService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package modu.menu.vote.service;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import modu.menu.choice.repository.ChoiceRepository;
import modu.menu.core.exception.Exception400;
import modu.menu.core.exception.Exception403;
import modu.menu.core.exception.Exception404;
import modu.menu.core.response.ErrorMessage;
import modu.menu.core.util.DistanceCalculator;
Expand Down Expand Up @@ -46,6 +49,7 @@ public class VoteService {
private final FoodRepository foodRepository;
private final UserRepository userRepository;
private final ParticipantRepository participantRepository;
private final HttpServletRequest request;

// 투표 생성
@Transactional
Expand Down Expand Up @@ -98,6 +102,28 @@ public void invite(Long voteId, Long userId) {
}
}

// 투표 종료
@Transactional
public void finishVote(Long voteId) {

Vote vote = voteRepository.findById(voteId).orElseThrow(
() -> new Exception404(ErrorMessage.NOT_EXIST_VOTE)
);
if (vote.getVoteStatus().equals(VoteStatus.END)) {
throw new Exception400(String.valueOf(voteId), ErrorMessage.CANT_FINISH_ALREADY_END_VOTE.getValue());
}

Long userId = (Long) request.getAttribute("userId");
Participant participant = participantRepository.findByUserIdAndVoteId(userId, voteId).orElseThrow(
() -> new Exception403(ErrorMessage.NOT_ALLOWED_USER)
);
if (participant.getVoteRole().equals(VoteRole.PARTICIPANT)) {
throw new Exception403(ErrorMessage.CANT_FINISH_BY_PARTICIPANT);
}

vote.updateVoteStatus(VoteStatus.END);
}

// 투표 결과 조회
public VoteResultResponse getResult(Long voteId, VoteResultRequest voteResultRequest) {

Expand Down
36 changes: 36 additions & 0 deletions src/test/java/modu/menu/vote/api/VoteControllerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -186,6 +187,41 @@ void inviteCantMatchUserIdWithTokenUserId() throws Exception {
.andExpect(jsonPath("$.message").value("토큰과 Path Variable의 id가 일치하지 않습니다."));
}

@DisplayName("투표를 종료하면 성공한다.")
@Test
void finishVote() throws Exception {
// given
Long voteId = 1L;

// when
doNothing().when(voteService).finishVote(anyLong());

// then
mockMvc.perform(patch("/api/vote/{voteId}/status", voteId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value(200))
.andExpect(jsonPath("$.reason").value("OK"))
.andExpect(jsonPath("$.data").isEmpty());
}

@DisplayName("종료하려는 투표의 ID가 양수여야 한다.")
@Test
void finishVoteWithZeroVoteId() throws Exception {
// given
Long voteId = 0L;

// when
doNothing().when(voteService).finishVote(anyLong());

// then
mockMvc.perform(patch("/api/vote/{voteId}/status", voteId))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.status").value(400))
.andExpect(jsonPath("$.reason").value("Bad Request"))
.andExpect(jsonPath("$.cause").exists())
.andExpect(jsonPath("$.message").value("voteId는 양수여야 합니다."));
}

@DisplayName("투표 결과를 조회하면 성공한다.")
@Test
void getResult() throws Exception {
Expand Down
Loading

0 comments on commit 65b63bd

Please sign in to comment.