diff --git a/src/main/java/com/debatetimer/domain/poll/Vote.java b/src/main/java/com/debatetimer/domain/poll/Vote.java index 6d14cf5f..6e5400e0 100644 --- a/src/main/java/com/debatetimer/domain/poll/Vote.java +++ b/src/main/java/com/debatetimer/domain/poll/Vote.java @@ -1,5 +1,6 @@ package com.debatetimer.domain.poll; +import java.time.LocalDateTime; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -12,12 +13,13 @@ public class Vote { private final VoteTeam team; private final ParticipantName name; private final ParticipateCode code; + private final LocalDateTime createdAt; public Vote(long pollId, VoteTeam team, String name, String code) { - this(null, pollId, team, name, code); + this(null, pollId, team, name, code, LocalDateTime.now()); } - public Vote(Long id, long pollId, VoteTeam team, String name, String code) { - this(id, pollId, team, new ParticipantName(name), new ParticipateCode(code)); + public Vote(Long id, long pollId, VoteTeam team, String name, String code, LocalDateTime createdAt) { + this(id, pollId, team, new ParticipantName(name), new ParticipateCode(code), createdAt); } } diff --git a/src/main/java/com/debatetimer/domain/poll/VoteInfo.java b/src/main/java/com/debatetimer/domain/poll/VoteInfo.java index 36734ebb..45232e05 100644 --- a/src/main/java/com/debatetimer/domain/poll/VoteInfo.java +++ b/src/main/java/com/debatetimer/domain/poll/VoteInfo.java @@ -1,19 +1,37 @@ package com.debatetimer.domain.poll; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import lombok.Getter; @Getter public class VoteInfo { + private static final Comparator VOTE_COMPARATOR = Comparator.comparing(Vote::getCreatedAt); + private static final long INITIAL_VOTE_COUNT = 0L; + private final long pollId; private final long totalCount; private final long prosCount; private final long consCount; + private final List voterNames; - public VoteInfo(long pollId, long prosCount, long consCount) { + public VoteInfo(long pollId, List votes) { + Map voteCounts = createVoteCounts(votes); this.pollId = pollId; - this.totalCount = prosCount + consCount; - this.prosCount = prosCount; - this.consCount = consCount; + this.totalCount = votes.size(); + this.prosCount = voteCounts.getOrDefault(VoteTeam.PROS, INITIAL_VOTE_COUNT); + this.consCount = voteCounts.getOrDefault(VoteTeam.CONS, INITIAL_VOTE_COUNT); + this.voterNames = votes.stream() + .sorted(VOTE_COMPARATOR) + .map(Vote::getName) + .toList(); + } + + private Map createVoteCounts(List votes) { + return votes.stream() + .collect(Collectors.groupingBy(Vote::getTeam, Collectors.counting())); } } diff --git a/src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java b/src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java index dc79f01f..436419f7 100644 --- a/src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java +++ b/src/main/java/com/debatetimer/domainrepository/poll/VoteDomainRepository.java @@ -3,7 +3,6 @@ import com.debatetimer.domain.poll.ParticipateCode; import com.debatetimer.domain.poll.Vote; import com.debatetimer.domain.poll.VoteInfo; -import com.debatetimer.domain.poll.VoteTeam; import com.debatetimer.entity.poll.PollEntity; import com.debatetimer.entity.poll.VoteEntity; import com.debatetimer.exception.custom.DTClientErrorException; @@ -12,8 +11,6 @@ import com.debatetimer.repository.poll.PollRepository; import com.debatetimer.repository.poll.VoteRepository; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Repository; @@ -27,16 +24,11 @@ public class VoteDomainRepository { private final RepositoryErrorDecoder errorDecoder; public VoteInfo findVoteInfoByPollId(long pollId) { - List pollVotes = voteRepository.findAllByPollId(pollId); - return countVotes(pollId, pollVotes); - } - - private VoteInfo countVotes(long pollId, List voteEntities) { - Map teamCount = voteEntities.stream() - .collect(Collectors.groupingBy(VoteEntity::getTeam, Collectors.counting())); - long prosCount = teamCount.getOrDefault(VoteTeam.PROS, 0L); - long consCount = teamCount.getOrDefault(VoteTeam.CONS, 0L); - return new VoteInfo(pollId, prosCount, consCount); + List pollVotes = voteRepository.findAllByPollId(pollId) + .stream() + .map(VoteEntity::toDomain) + .toList(); + return new VoteInfo(pollId, pollVotes); } public boolean isExists(long pollId, ParticipateCode code) { diff --git a/src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java b/src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java index 69f3f6ae..87340c65 100644 --- a/src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java +++ b/src/main/java/com/debatetimer/dto/poll/response/PollInfoResponse.java @@ -1,8 +1,10 @@ package com.debatetimer.dto.poll.response; +import com.debatetimer.domain.poll.ParticipantName; import com.debatetimer.domain.poll.Poll; import com.debatetimer.domain.poll.PollStatus; import com.debatetimer.domain.poll.VoteInfo; +import java.util.List; public record PollInfoResponse( long id, @@ -11,7 +13,8 @@ public record PollInfoResponse( String consTeamName, long totalCount, long prosCount, - long consCount + long consCount, + List voterNames ) { public PollInfoResponse(Poll poll, VoteInfo voteInfo) { @@ -22,7 +25,10 @@ public PollInfoResponse(Poll poll, VoteInfo voteInfo) { poll.getConsTeamName().getValue(), voteInfo.getTotalCount(), voteInfo.getProsCount(), - voteInfo.getConsCount() + voteInfo.getConsCount(), + voteInfo.getVoterNames().stream() + .map(ParticipantName::getValue) + .toList() ); } } diff --git a/src/main/java/com/debatetimer/entity/poll/VoteEntity.java b/src/main/java/com/debatetimer/entity/poll/VoteEntity.java index 0b0f26c7..0e6b6d43 100644 --- a/src/main/java/com/debatetimer/entity/poll/VoteEntity.java +++ b/src/main/java/com/debatetimer/entity/poll/VoteEntity.java @@ -2,6 +2,7 @@ import com.debatetimer.domain.poll.Vote; import com.debatetimer.domain.poll.VoteTeam; +import com.debatetimer.entity.BaseTimeEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -28,7 +29,7 @@ }) @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor -public class VoteEntity { +public class VoteEntity extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -55,6 +56,6 @@ public VoteEntity(Vote vote, PollEntity pollEntity) { } public Vote toDomain() { - return new Vote(id, poll.getId(), team, name, participateCode); + return new Vote(id, poll.getId(), team, name, participateCode, getCreatedAt()); } } diff --git a/src/main/resources/db/migration/V14__add_time_column_into_vote.sql b/src/main/resources/db/migration/V14__add_time_column_into_vote.sql new file mode 100644 index 00000000..4663c8d2 --- /dev/null +++ b/src/main/resources/db/migration/V14__add_time_column_into_vote.sql @@ -0,0 +1,6 @@ +ALTER TABLE vote + ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP; + +ALTER TABLE vote + ADD COLUMN modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; + diff --git a/src/test/java/com/debatetimer/controller/poll/PollControllerTest.java b/src/test/java/com/debatetimer/controller/poll/PollControllerTest.java index ee3fad9b..59d32cff 100644 --- a/src/test/java/com/debatetimer/controller/poll/PollControllerTest.java +++ b/src/test/java/com/debatetimer/controller/poll/PollControllerTest.java @@ -10,6 +10,7 @@ import com.debatetimer.dto.poll.response.PollInfoResponse; import com.debatetimer.entity.customize.CustomizeTableEntity; import com.debatetimer.entity.poll.PollEntity; +import com.debatetimer.entity.poll.VoteEntity; import io.restassured.http.ContentType; import io.restassured.http.Headers; import org.junit.jupiter.api.Nested; @@ -44,9 +45,9 @@ class GetPollInfo { Member member = memberGenerator.generate("email@email.com"); CustomizeTableEntity table = customizeTableEntityGenerator.generate(member); PollEntity pollEntity = pollEntityGenerator.generate(table, PollStatus.PROGRESS); - voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "콜리"); - voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "비토"); - voteEntityGenerator.generate(pollEntity, VoteTeam.CONS, "커찬"); + VoteEntity voter1 = voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "콜리"); + VoteEntity voter2 = voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "비토"); + VoteEntity voter3 = voteEntityGenerator.generate(pollEntity, VoteTeam.CONS, "커찬"); Headers headers = headerGenerator.generateAccessTokenHeader(member); PollInfoResponse response = given() @@ -64,7 +65,9 @@ class GetPollInfo { () -> assertThat(response.status()).isEqualTo(pollEntity.getStatus()), () -> assertThat(response.totalCount()).isEqualTo(3L), () -> assertThat(response.prosCount()).isEqualTo(2L), - () -> assertThat(response.consCount()).isEqualTo(1L) + () -> assertThat(response.consCount()).isEqualTo(1L), + () -> assertThat(response.voterNames()).containsExactly( + voter1.getName(), voter2.getName(), voter3.getName()) ); } } diff --git a/src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java b/src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java index e0cd3b42..cd8eaad4 100644 --- a/src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java +++ b/src/test/java/com/debatetimer/controller/poll/PollDocumentTest.java @@ -4,6 +4,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.doReturn; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.payload.JsonFieldType.ARRAY; import static org.springframework.restdocs.payload.JsonFieldType.NUMBER; import static org.springframework.restdocs.payload.JsonFieldType.STRING; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; @@ -18,6 +19,7 @@ import com.debatetimer.dto.poll.response.PollCreateResponse; import com.debatetimer.dto.poll.response.PollInfoResponse; import io.restassured.http.ContentType; +import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; @@ -85,7 +87,8 @@ class GetPollInfo { fieldWithPath("consTeamName").type(STRING).description("반대측 팀 이름"), fieldWithPath("totalCount").type(NUMBER).description("전체 투표 수"), fieldWithPath("prosCount").type(NUMBER).description("찬성 투표 수"), - fieldWithPath("consCount").type(NUMBER).description("반대 투표 수") + fieldWithPath("consCount").type(NUMBER).description("반대 투표 수"), + fieldWithPath("voterNames").type(ARRAY).description("투표자 이름 정보") ); @Test @@ -97,7 +100,8 @@ class GetPollInfo { "반대", 3L, 2L, - 1L + 1L, + List.of("콜리", "비토", "커찬") ); doReturn(response).when(pollService).getPollInfo(anyLong(), any(Member.class)); @@ -136,7 +140,8 @@ class FinishPoll { fieldWithPath("consTeamName").type(STRING).description("반대측 팀 이름"), fieldWithPath("totalCount").type(NUMBER).description("전체 투표 수"), fieldWithPath("prosCount").type(NUMBER).description("찬성 투표 수"), - fieldWithPath("consCount").type(NUMBER).description("반대 투표 수") + fieldWithPath("consCount").type(NUMBER).description("반대 투표 수"), + fieldWithPath("voterNames").type(ARRAY).description("투표자 이름 정보") ); @Test @@ -148,7 +153,9 @@ class FinishPoll { "반대", 3L, 2L, - 1L + 1L, + List.of("콜리", "비토", "커찬") + ); doReturn(response).when(pollService).finishPoll(anyLong(), any(Member.class)); diff --git a/src/test/java/com/debatetimer/domain/poll/VoteInfoTest.java b/src/test/java/com/debatetimer/domain/poll/VoteInfoTest.java new file mode 100644 index 00000000..a8b5b5cc --- /dev/null +++ b/src/test/java/com/debatetimer/domain/poll/VoteInfoTest.java @@ -0,0 +1,39 @@ +package com.debatetimer.domain.poll; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class VoteInfoTest { + + @Nested + class getVoterNames { + + @Test + void 생성_순으로_투표자_이름을_정렬한다() { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime oneMinutesAgo = now.minusMinutes(1); + LocalDateTime twoMinutesAgo = now.minusMinutes(2); + long pollId = 1L; + Vote nowVote = new Vote(1L, pollId, VoteTeam.PROS, "콜리1", "code1", now); + Vote oneMinutesAgoVote = new Vote(2L, pollId, VoteTeam.PROS, "콜리2", "code2", oneMinutesAgo); + Vote twoMinutesAgoVote = new Vote(3L, pollId, VoteTeam.PROS, "콜리3", "code3", twoMinutesAgo); + + VoteInfo voteInfo = new VoteInfo(pollId, List.of(nowVote, oneMinutesAgoVote, twoMinutesAgoVote)); + List voterNames = voteInfo.getVoterNames() + .stream() + .map(ParticipantName::getValue) + .toList(); + + assertThat(voterNames) + .containsExactly( + twoMinutesAgoVote.getName().getValue(), + oneMinutesAgoVote.getName().getValue(), + nowVote.getName().getValue() + ); + } + } +} diff --git a/src/test/java/com/debatetimer/service/poll/PollServiceTest.java b/src/test/java/com/debatetimer/service/poll/PollServiceTest.java index 4dd3e913..678547b4 100644 --- a/src/test/java/com/debatetimer/service/poll/PollServiceTest.java +++ b/src/test/java/com/debatetimer/service/poll/PollServiceTest.java @@ -10,6 +10,7 @@ import com.debatetimer.dto.poll.response.PollInfoResponse; import com.debatetimer.entity.customize.CustomizeTableEntity; import com.debatetimer.entity.poll.PollEntity; +import com.debatetimer.entity.poll.VoteEntity; import com.debatetimer.service.BaseServiceTest; import java.util.Optional; import org.junit.jupiter.api.Nested; @@ -44,9 +45,9 @@ class GetPollInfo { Member member = memberGenerator.generate("email@email.com"); CustomizeTableEntity table = customizeTableEntityGenerator.generate(member); PollEntity pollEntity = pollEntityGenerator.generate(table, PollStatus.PROGRESS); - voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "콜리"); - voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "비토"); - voteEntityGenerator.generate(pollEntity, VoteTeam.CONS, "커찬"); + VoteEntity voter1 = voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "콜리"); + VoteEntity voter2 = voteEntityGenerator.generate(pollEntity, VoteTeam.PROS, "비토"); + VoteEntity voter3 = voteEntityGenerator.generate(pollEntity, VoteTeam.CONS, "커찬"); PollInfoResponse pollInfo = pollService.getPollInfo(table.getId(), member); @@ -57,7 +58,9 @@ class GetPollInfo { () -> assertThat(pollInfo.status()).isEqualTo(pollEntity.getStatus()), () -> assertThat(pollInfo.totalCount()).isEqualTo(3L), () -> assertThat(pollInfo.prosCount()).isEqualTo(2L), - () -> assertThat(pollInfo.consCount()).isEqualTo(1L) + () -> assertThat(pollInfo.consCount()).isEqualTo(1L), + () -> assertThat(pollInfo.voterNames()).containsExactly( + voter1.getName(), voter2.getName(), voter3.getName()) ); } }