diff --git a/README.md b/README.md
index 7125e97a..ad18e211 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,78 @@
-# debate-timer
\ No newline at end of file
+# Debate Timer Backend
+
+## 프로젝트 소개
+
+디베이트 타이머는 더 쉬운 토론 진행을 위한, 오직 토론을 위한 타이머입니다.
+
+## 기술 스택
+
+### Backend
+
+
+
+### Database
+
+
+
+### DevOps & Monitoring
+
+
+
+### Documentation & Testing
+
+
+
+## 프로젝트 구조
+
+```
+src/
+├── main/
+│ ├── java/com/debatetimer/
+│ │ ├── aop/ # AOP 및 로깅 관련
+│ │ ├── client/ # 외부 API 클라이언트
+│ │ ├── config/ # 설정 클래스
+│ │ ├── controller/ # REST API 컨트롤러
+│ │ │ ├── auth/ # 인증 관련
+│ │ │ ├── customize/ # 토론 테이블 커스터마이징
+│ │ │ ├── member/ # 회원 관리
+│ │ │ └── poll/ # 투표 관리
+│ │ ├── domain/ # 도메인 모델
+│ │ ├── domainrepository/ # 도메인 레포지토리
+│ │ ├── dto/ # 데이터 전송 객체
+│ │ ├── entity/ # JPA 엔티티
+│ │ ├── exception/ # 예외 처리
+│ │ ├── repository/ # JPA 레포지토리
+│ │ └── service/ # 비즈니스 로직
+│ └── resources/
+│ ├── application*.yml # 환경별 설정
+│ ├── logging/ # 로깅 설정
+│ └── db/migration/ # Flyway 마이그레이션
+└── test/ # 테스트 코드
+```
+
+## Infra & Deployment
+
+
+
+## BE 팀원 소개
+
+|
|
|
|
+|:-------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------:|:-------------------------------------------------------------------------------------------:|
+| [콜리](https://github.com/coli-geonwoo) | [커찬](https://github.com/leegwichan) | [비토](https://github.com/unifolio0) |
diff --git a/docs/debate_timer_infra_v0-1.png b/docs/debate_timer_infra_v0-1.png
new file mode 100644
index 00000000..9719644f
Binary files /dev/null and b/docs/debate_timer_infra_v0-1.png differ
diff --git a/docs/debate_timer_infra_v0.png b/docs/debate_timer_infra_v0.png
new file mode 100644
index 00000000..7a12ba76
Binary files /dev/null and b/docs/debate_timer_infra_v0.png differ
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())
);
}
}