Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.time.LocalDateTime;

import com.ai.lawyer.global.util.AuthUtil;
import org.springframework.dao.DataIntegrityViolationException;

@Service
@Transactional
Expand Down Expand Up @@ -106,61 +107,63 @@ public PollVoteDto vote(Long pollId, Long pollItemsId, Long memberId) {
}
PollOptions pollOptions = pollOptionsRepository.findById(pollItemsId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "투표 항목을 찾을 수 없습니다."));
if (pollOptions.getPoll() == null || !pollOptions.getPoll().getPollId().equals(pollId)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "투표 항목이 해당 투표에 속하지 않습니다.");
}

Member member = AuthUtil.getMemberOrThrow(memberId);
// USER 또는 ADMIN만 투표 가능
if (!(member.getRole().name().equals("USER") || member.getRole().name().equals("ADMIN"))) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "투표 권한이 없습니다.");
}

try {
// 기존 투표 내역 조회
var existingVoteOpt = pollVoteRepository.findByMemberIdAndPoll_PollId(memberId, pollId);
if (existingVoteOpt.isPresent()) {
PollVote existingVote = existingVoteOpt.get();
if (existingVote.getPollOptions().getPollItemsId().equals(pollItemsId)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미 투표하셨습니다.");
} else {
pollVoteRepository.deleteByMemberIdAndPoll_PollId(memberId, pollId);
PollVote pollVote = PollVote.builder()
.poll(poll)
.pollOptions(pollOptions)
.memberId(memberId)
.build();
PollVote savedVote = pollVoteRepository.save(pollVote);
Long voteCount = pollVoteRepository.countByPollOptionId(pollItemsId);
return PollVoteDto.builder()
.pollVoteId(savedVote.getPollVoteId())
.pollId(pollId)
.pollItemsId(pollItemsId)
.memberId(memberId)
.voteCount(voteCount)
.message("투표 항목을 변경하였습니다.")
.build();
}
// 1) 기존 투표가 있는지 조회
var existingOpt = pollVoteRepository.findByMemberIdAndPoll_PollId(memberId, pollId);
if (existingOpt.isPresent()) {
return handleExistingVote(existingOpt.get(), pollOptions, pollId, pollItemsId, memberId);
}
// 기존 투표 내역이 없으면 정상 투표
PollVote pollVote = PollVote.builder()
// 2) 신규 투표 생성
PollVote newVote = PollVote.builder()
.poll(poll)
.pollOptions(pollOptions)
.memberId(memberId)
.build();
PollVote savedVote = pollVoteRepository.save(pollVote);
Long voteCount = pollVoteRepository.countByPollOptionId(pollItemsId);
return PollVoteDto.builder()
.pollVoteId(savedVote.getPollVoteId())
.pollId(pollId)
.pollItemsId(pollItemsId)
.memberId(memberId)
.voteCount(voteCount)
.message("투표가 완료되었습니다.")
.build();
} catch (org.springframework.dao.DataIntegrityViolationException e) {
// 동시성 문제로 인한 중복 투표 시도 (unique constraint violation)
PollVote saved = pollVoteRepository.save(newVote);
return buildPollVote(saved, pollId, pollItemsId, memberId, "투표가 완료되었습니다.");
} catch (DataIntegrityViolationException e) {
// 동시성(경합)으로 인해 이미 다른 쓰레드가 투표를 만들어 중복 제약에 걸린 경우 복구 처리
log.warn("중복 투표 시도 감지 - memberId: {}, pollId: {}", memberId, pollId, e);
var existingAfterOpt = pollVoteRepository.findByMemberIdAndPoll_PollId(memberId, pollId);
if (existingAfterOpt.isPresent()) {
return handleExistingVote(existingAfterOpt.get(), pollOptions, pollId, pollItemsId, memberId);
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "이미 투표하셨습니다. 중복 투표는 불가능합니다.");
}
}

private PollVoteDto handleExistingVote(PollVote existing, PollOptions targetOption,
Long pollId, Long pollItemsId, Long memberId) {
// 동일 항목이면 idempotent 응답
if (existing.getPollOptions().getPollItemsId().equals(pollItemsId)) {
return buildPollVote(existing, pollId, pollItemsId, memberId, "이미 해당 항목에 투표하셨습니다.");
}
existing.setPollOptions(targetOption);
PollVote saved = pollVoteRepository.save(existing);
return buildPollVote(saved, pollId, pollItemsId, memberId, "투표 항목을 변경하였습니다.");
}

private PollVoteDto buildPollVote(PollVote vote, Long pollId, Long pollItemsId, Long memberId, String message) {
Long voteCount = pollVoteRepository.countByPollOptionId(pollItemsId);
return PollVoteDto.builder()
.pollVoteId(vote != null ? vote.getPollVoteId() : null)
.pollId(pollId)
.pollItemsId(pollItemsId)
.memberId(memberId)
.voteCount(voteCount)
.message(message)
.build();
}

@Override
public PollVoteDto voteByIndex(Long pollId, int index, Long memberId) {
List<PollOptions> options = getPollOptions(pollId);
Expand Down