Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9f91e6c
refactor: poll 엔티티에 개최자 id 추가
coli-geonwoo Jul 23, 2025
d311049
feat: poll domain 추가
coli-geonwoo Jul 23, 2025
b480dd4
feat: vote domain 추가
coli-geonwoo Jul 23, 2025
980798c
feat: controller 계층 구현
coli-geonwoo Jul 23, 2025
9c0bf91
feat: VoteInfo 계층 구현
coli-geonwoo Jul 23, 2025
a1b559e
feat: repository 계층 구현
coli-geonwoo Jul 23, 2025
61fc36d
feat: poll 생성로직 구현
coli-geonwoo Jul 23, 2025
4e36d7a
feat: poll 서비스 로직 구현
coli-geonwoo Jul 23, 2025
51a846f
feat: poll 컨트롤러 로직 개발
coli-geonwoo Jul 23, 2025
47798a6
feat: 참여자 이름, 코드 테스트 작성
coli-geonwoo Jul 23, 2025
ac57a53
feat: 참여자 이름, 코드 테스트 작성
coli-geonwoo Jul 23, 2025
4ee38ed
chore: basetest 디렉토리 이동
coli-geonwoo Jul 23, 2025
b2353b1
test: poll domainRepository 테스트 작성
coli-geonwoo Jul 23, 2025
7f54c78
test: PollServiceTest 작성
coli-geonwoo Jul 23, 2025
b0fb06c
rename: Jpa 벤더 표기 삭제
coli-geonwoo Jul 23, 2025
aed0b79
test: controllerTest 작성
coli-geonwoo Jul 23, 2025
f973177
fix: 실패하는 테스트 수정
coli-geonwoo Jul 23, 2025
22c2302
test: 문서화 코드 작성
coli-geonwoo Jul 23, 2025
f4f9866
refactor: 투표 생성시 다른 정보들도 같이 넘기도록 수정
coli-geonwoo Jul 24, 2025
8991862
rename : updateToDone > finishPoll
coli-geonwoo Jul 24, 2025
117a5f1
rename : resolveVoteInfo > countVotes
coli-geonwoo Jul 24, 2025
c9666f9
style: 무의미한 개행 제거
coli-geonwoo Jul 24, 2025
32cfd07
style: 무의미한 개행 제거
coli-geonwoo Jul 24, 2025
0d58848
refactor: 초기 생성자 추가
coli-geonwoo Jul 24, 2025
9134f84
rename : readPollInfo > getPollInfo
coli-geonwoo Jul 24, 2025
7da7c6e
test: 선거완료 문서화 코드 response 변경
coli-geonwoo Jul 24, 2025
de1c89f
test: nullandemptyandblanksource 사용하도록 변경
coli-geonwoo Jul 24, 2025
538be99
test: CRUD 테스트를 컨벤션에 맞도록 변경
coli-geonwoo Jul 24, 2025
486abf1
test: 테스트 생성자 변환
coli-geonwoo Jul 25, 2025
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
49 changes: 49 additions & 0 deletions src/main/java/com/debatetimer/controller/poll/PollController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.debatetimer.controller.poll;

import com.debatetimer.controller.auth.AuthMember;
import com.debatetimer.domain.member.Member;
import com.debatetimer.dto.poll.response.PollCreateResponse;
import com.debatetimer.dto.poll.response.PollInfoResponse;
import com.debatetimer.service.poll.PollService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class PollController {

private final PollService pollService;

@PostMapping("/api/polls/{tableId}")
@ResponseStatus(HttpStatus.CREATED)
public PollCreateResponse createPoll(
@AuthMember Member member,
@PathVariable(name = "tableId") long tableId
) {
return pollService.create(tableId, member);
}

@GetMapping("/api/polls/{pollId}")
@ResponseStatus(HttpStatus.OK)
public PollInfoResponse getPollInfo(
@AuthMember Member member,
@PathVariable(name = "pollId") long pollId
) {
return pollService.getPollInfo(pollId, member);
}

@PatchMapping("/api/polls/{pollId}")
@ResponseStatus(HttpStatus.OK)
public PollInfoResponse finishPoll(
@AuthMember Member member,
@PathVariable(name = "pollId") long pollId
) {
return pollService.finishPoll(pollId, member);
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/debatetimer/domain/poll/ParticipantName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.debatetimer.domain.poll;

import com.debatetimer.exception.custom.DTClientErrorException;
import com.debatetimer.exception.errorcode.ClientErrorCode;
import lombok.Getter;

@Getter
public class ParticipantName {

private final String value;

public ParticipantName(String value) {
validateName(value);
this.value = value;
}

private void validateName(String value) {
if (value == null || value.isBlank()) {
throw new DTClientErrorException(ClientErrorCode.INVALID_POLL_PARTICIPANT_NAME);
}
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/debatetimer/domain/poll/ParticipateCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.debatetimer.domain.poll;

import com.debatetimer.exception.custom.DTClientErrorException;
import com.debatetimer.exception.errorcode.ClientErrorCode;
import lombok.Getter;

@Getter
public class ParticipateCode {

private final String value;

public ParticipateCode(String value) {
validateName(value);
this.value = value;
}

private void validateName(String value) {
if (value == null || value.isBlank()) {
throw new DTClientErrorException(ClientErrorCode.INVALID_POLL_PARTICIPANT_CODE);
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/debatetimer/domain/poll/Poll.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.debatetimer.domain.poll;

import com.debatetimer.domain.customize.Agenda;
import com.debatetimer.domain.customize.TeamName;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class Poll {

private final Long id;
private final long tableId;
private final long memberId;
private final PollStatus status;
private final TeamName prosTeamName;
private final TeamName consTeamName;
private final Agenda agenda;

public Poll(long tableId, long memberId, String prosTeamName, String consTeamName, String agenda) {
this(null, tableId, memberId, PollStatus.PROGRESS,
new TeamName(prosTeamName), new TeamName(consTeamName), new Agenda(agenda));
}

public Poll(Long id, long tableId, long memberId, PollStatus status,
String prosTeamName, String consTeamName, String agenda) {
this(id, tableId, memberId, status, new TeamName(prosTeamName), new TeamName(consTeamName), new Agenda(agenda));
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/debatetimer/domain/poll/Vote.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.debatetimer.domain.poll;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class Vote {

private final Long id;
private final long pollId;
private final VoteTeam team;
private final ParticipantName name;
private final ParticipateCode code;

public Vote(Long id, long pollId, VoteTeam team, String name, String code) {
this(id, pollId, team, new ParticipantName(name), new ParticipateCode(code));
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/debatetimer/domain/poll/VoteInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.debatetimer.domain.poll;

import lombok.Getter;

@Getter
public class VoteInfo {

private final long pollId;
private final long totalCount;
private final long prosCount;
private final long consCount;
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 서비스에서 검증해서 따로 검증로직이 없는 건가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[질문 ✋ ]

음.. 검증로직을 원래 넣었었는데 서로 시각이 다른 부분이 있을 것 같아 비토랑 커찬 시각이 궁금하네요.

해당 count 값은 애플리케이션 서버 로직 내에서 완전히 통제되는 값이에요. 즉, 외부 변인에 의해 수정될 여지가 크게 없다고 판단했어요.
또, 만약 검증로직을 넣는다면 ClientError인지, Server Error로 처리해야할지가 애매했습니다.

  1. 검증로직 필요하다고 보는가?
  2. 만약 양수 검증을 한다면 Server Error인가, Client Error인가?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 "DB에 있는 값을 보여주기 위한 객체"에는 유효성 검사를 잘 안하는 것 같아요.차라리 동작 안하는 것보다는 뭐라도 어떻게는 동작하게 하자는 느낌?
만약에 이 객체가 유저가 입력을 위한 객체로도 쓰인다고 하면, 그 때 추가하지 않을까 싶네요.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 저는 넣는 편이긴 합니다. 다만 그 검증은 사용자에게 보여주기보단 다른 개발자의 실수 방지 용도에 더 가깝긴 할 것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일단 voter api를 만들기 위해 놔두겠습니다. 백엔드 회의 때 논의해보시죵


public VoteInfo(long pollId, long prosCount, long consCount) {
this.pollId = pollId;
this.totalCount = prosCount + consCount;
this.prosCount = prosCount;
this.consCount = consCount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.debatetimer.domainrepository.poll;

import com.debatetimer.domain.customize.CustomizeTable;
import com.debatetimer.domain.member.Member;
import com.debatetimer.exception.custom.DTClientErrorException;
import com.debatetimer.exception.errorcode.ClientErrorCode;
import com.debatetimer.repository.customize.CustomizeTableRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@RequiredArgsConstructor
public class CustomizeTableDomainRepository {

private final CustomizeTableRepository customizeTableRepository;

@Transactional(readOnly = true)
public CustomizeTable getByIdAndMember(long tableId, Member member) {
return customizeTableRepository.findByIdAndMember(tableId, member)
.orElseThrow(() -> new DTClientErrorException(ClientErrorCode.TABLE_NOT_FOUND))
.toDomain();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.debatetimer.domainrepository.poll;

import com.debatetimer.domain.poll.Poll;
import com.debatetimer.entity.poll.PollEntity;
import com.debatetimer.exception.custom.DTClientErrorException;
import com.debatetimer.exception.errorcode.ClientErrorCode;
import com.debatetimer.repository.poll.PollRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@RequiredArgsConstructor
public class PollDomainRepository {

private final PollRepository pollRepository;

@Transactional
public Poll create(Poll poll) {
PollEntity pollEntity = new PollEntity(poll);
return pollRepository.save(pollEntity)
.toDomain();
}

@Transactional(readOnly = true)
public Poll getByIdAndMemberId(long id, long memberId) {
return findPoll(id, memberId)
.toDomain();
}

@Transactional
public Poll finishPoll(long pollId, long memberId) {
PollEntity pollEntity = findPoll(pollId, memberId);
pollEntity.updateToDone();
return pollEntity.toDomain();
}

private PollEntity findPoll(long pollId, long memberId) {
return pollRepository.findByIdAndMemberId(pollId, memberId)
.orElseThrow(() -> new DTClientErrorException(ClientErrorCode.POLL_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.debatetimer.domainrepository.poll;

import com.debatetimer.domain.poll.VoteInfo;
import com.debatetimer.domain.poll.VoteTeam;
import com.debatetimer.entity.poll.VoteEntity;
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.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class VoteDomainRepository {

private final VoteRepository voteRepository;

public VoteInfo findVoteInfoByPollId(long pollId) {
List<VoteEntity> pollVotes = voteRepository.findAllByPollId(pollId);
return countVotes(pollId, pollVotes);
}

private VoteInfo countVotes(long pollId, List<VoteEntity> voteEntities) {
Map<VoteTeam, Long> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.debatetimer.dto.poll.response;

import com.debatetimer.domain.poll.Poll;
import com.debatetimer.domain.poll.PollStatus;

public record PollCreateResponse(
long id,
PollStatus status,
String prosTeamName,
String consTeamName
) {

public PollCreateResponse(Poll poll) {
this(poll.getId(), poll.getStatus(), poll.getProsTeamName().getValue(), poll.getConsTeamName().getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.debatetimer.dto.poll.response;

import com.debatetimer.domain.poll.Poll;
import com.debatetimer.domain.poll.PollStatus;
import com.debatetimer.domain.poll.VoteInfo;

public record PollInfoResponse(
long id,
PollStatus status,
String prosTeamName,
String consTeamName,
long totalCount,
long prosCount,
long consCount
) {

public PollInfoResponse(Poll poll, VoteInfo voteInfo) {
this(
poll.getId(),
poll.getStatus(),
poll.getProsTeamName().getValue(),
poll.getConsTeamName().getValue(),
voteInfo.getTotalCount(),
voteInfo.getProsCount(),
voteInfo.getConsCount()
);
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/debatetimer/entity/poll/PollEntity.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.debatetimer.entity.poll;

import com.debatetimer.domain.poll.Poll;
import com.debatetimer.domain.poll.PollStatus;
import com.debatetimer.entity.customize.BaseTimeEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
Expand All @@ -12,9 +14,11 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@Table(name = "poll")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PollEntity extends BaseTimeEntity {
Expand All @@ -23,8 +27,12 @@ public class PollEntity extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "table_id")
private long tableId;

@Column(name = "member_id")
private long memberId;

@NotNull
@Enumerated(EnumType.STRING)
private PollStatus status;
Expand All @@ -36,4 +44,22 @@ public class PollEntity extends BaseTimeEntity {
private String consTeamName;

private String agenda;

public PollEntity(Poll poll) {
this.id = poll.getId();
this.tableId = poll.getTableId();
this.memberId = poll.getMemberId();
this.status = poll.getStatus();
this.prosTeamName = poll.getProsTeamName().getValue();
this.consTeamName = poll.getConsTeamName().getValue();
this.agenda = poll.getAgenda().getValue();
}

public void updateToDone() {
this.status = PollStatus.DONE;
}

public Poll toDomain() {
return new Poll(id, tableId, memberId, status, prosTeamName, consTeamName, agenda);
}
}
Loading