Skip to content

Commit 443c81d

Browse files
authored
Merge pull request #223 from DevKor-github/feat/vote
Feat: 투표 관리용 api
2 parents 94c8c19 + 7965d4c commit 443c81d

7 files changed

Lines changed: 179 additions & 16 deletions

File tree

src/main/java/devkor/com/teamcback/domain/vote/controller/AdminVoteController.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
package devkor.com.teamcback.domain.vote.controller;
22

33
import devkor.com.teamcback.domain.vote.dto.response.ChangeVoteStatusRes;
4+
import devkor.com.teamcback.domain.vote.dto.response.GetVoteRes;
5+
import devkor.com.teamcback.domain.vote.entity.VoteStatus;
46
import devkor.com.teamcback.domain.vote.service.VoteService;
57
import devkor.com.teamcback.global.response.CommonResponse;
8+
import devkor.com.teamcback.global.response.CursorPageRes;
69
import io.swagger.v3.oas.annotations.Operation;
710
import io.swagger.v3.oas.annotations.Parameter;
811
import io.swagger.v3.oas.annotations.media.Content;
912
import io.swagger.v3.oas.annotations.media.Schema;
1013
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1114
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1215
import lombok.RequiredArgsConstructor;
13-
import org.springframework.web.bind.annotation.GetMapping;
14-
import org.springframework.web.bind.annotation.PathVariable;
15-
import org.springframework.web.bind.annotation.RequestMapping;
16-
import org.springframework.web.bind.annotation.RestController;
16+
import org.springframework.web.bind.annotation.*;
1717

1818
@RestController
1919
@RequiredArgsConstructor
@@ -30,10 +30,33 @@ public class AdminVoteController {
3030
@ApiResponse(responseCode = "404", description = "Not Found",
3131
content = @Content(schema = @Schema(implementation = CommonResponse.class))),
3232
})
33-
@GetMapping("/{voteId}")
33+
@PatchMapping("/{voteId}")
3434
public CommonResponse<ChangeVoteStatusRes> changeVoteStatus(
3535
@Parameter(description = "투표 ID", example = "1")
36-
@PathVariable(name = "voteId") Long voteId) {
37-
return CommonResponse.success(voteService.changeVoteStatus(voteId));
36+
@PathVariable(name = "voteId") Long voteId,
37+
@Parameter(description = "투표 상태", example = "REFLECTED")
38+
@RequestParam(name = "status") VoteStatus status) {
39+
return CommonResponse.success(voteService.changeVoteStatus(voteId, status));
40+
}
41+
42+
/**
43+
* 투표 조회
44+
*/
45+
@Operation(summary = "투표 조회", description = "투표 조회")
46+
@ApiResponses(value = {
47+
@ApiResponse(responseCode = "200", description = "정상 처리 되었습니다."),
48+
@ApiResponse(responseCode = "404", description = "Not Found",
49+
content = @Content(schema = @Schema(implementation = CommonResponse.class))),
50+
})
51+
@GetMapping()
52+
public CommonResponse<CursorPageRes<GetVoteRes>> getVoteList(
53+
@Parameter(description = "투표 상태", example = "REFLECTED")
54+
@RequestParam(name = "status", required = false) VoteStatus status,
55+
@Parameter(description = "마지막 조회 투표 ID", example = "0")
56+
@RequestParam(name = "lastVoteId", defaultValue = "0", required = false) Long lastVoteId,
57+
@Parameter(description = "조회 size", example = "20")
58+
@RequestParam(name = "size", defaultValue = "20", required = false) int size
59+
) {
60+
return CommonResponse.success(voteService.getVoteList(status, lastVoteId, size));
3861
}
3962
}

src/main/java/devkor/com/teamcback/domain/vote/entity/Vote.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import lombok.Setter;
99

1010
import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED;
11-
import static devkor.com.teamcback.domain.vote.entity.VoteStatus.OPEN;
1211
import static devkor.com.teamcback.global.response.ResultCode.CLOSED_VOTE;
1312

1413
@Entity
@@ -35,8 +34,7 @@ public void checkStatus() {
3534
if(this.status == CLOSED) throw new GlobalException(CLOSED_VOTE);
3635
}
3736

38-
public void changeStatus() {
39-
if(this.status == CLOSED) this.status = OPEN;
40-
else this.status = CLOSED;
37+
public void changeStatus(VoteStatus status) {
38+
this.status = status;
4139
}
4240
}

src/main/java/devkor/com/teamcback/domain/vote/entity/VoteStatus.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
@AllArgsConstructor
88
public enum VoteStatus {
99
OPEN("진행 중"),
10-
CLOSED("종료");
10+
CLOSED("종료"),
11+
REFLECTED("결과 반영됨");
1112

1213
private final String name;
1314
}

src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepository.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,17 @@
22

33

44
import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes;
5+
import devkor.com.teamcback.domain.vote.entity.Vote;
6+
import devkor.com.teamcback.domain.vote.entity.VoteStatus;
57

68
import java.util.List;
79

810
public interface CustomVoteRepository {
911
List<GetVoteOptionRes> getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId);
12+
13+
List<Vote> getVoteByStatusWithPage(VoteStatus status, Long voteTopicId, int size);
14+
15+
List<GetVoteOptionRes> getVoteOptionByVoteId(Long voteId);
16+
17+
List<GetVoteOptionRes> getVoteOptionByVoteIdOrderByCount(Long voteId);
1018
}

src/main/java/devkor/com/teamcback/domain/vote/repository/CustomVoteRepositoryImpl.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package devkor.com.teamcback.domain.vote.repository;
22

3+
import com.querydsl.core.BooleanBuilder;
4+
import com.querydsl.core.types.dsl.BooleanExpression;
35
import com.querydsl.jpa.impl.JPAQueryFactory;
46

7+
import static devkor.com.teamcback.domain.place.entity.QPlace.place;
58
import static devkor.com.teamcback.domain.vote.entity.QVoteOption.voteOption;
69
import static devkor.com.teamcback.domain.vote.entity.QVoteRecord.voteRecord;
710
import static devkor.com.teamcback.domain.vote.entity.QVote.vote;
811

12+
import devkor.com.teamcback.domain.place.entity.Place;
13+
import devkor.com.teamcback.domain.place.entity.PlaceType;
914
import devkor.com.teamcback.domain.vote.dto.response.GetVoteOptionRes;
1015
import devkor.com.teamcback.domain.vote.dto.response.QGetVoteOptionRes;
16+
import devkor.com.teamcback.domain.vote.entity.Vote;
17+
import devkor.com.teamcback.domain.vote.entity.VoteStatus;
1118
import lombok.RequiredArgsConstructor;
1219
import org.springframework.stereotype.Repository;
1320

@@ -45,4 +52,82 @@ public List<GetVoteOptionRes> getVoteOptionsByPlaceByVoteTopicIdAndPlaceId(Long
4552
)
4653
.fetch();
4754
}
55+
56+
@Override
57+
public List<Vote> getVoteByStatusWithPage(VoteStatus status, Long lastVoteId, int size) {
58+
BooleanBuilder builder = new BooleanBuilder();
59+
builder.and(gtVoteCursor(lastVoteId));
60+
61+
if (status != null) {
62+
builder.and(vote.status.eq(status));
63+
}
64+
65+
return jpaQueryFactory
66+
.selectFrom(vote)
67+
.where(
68+
gtVoteCursor(lastVoteId).and(builder)
69+
)
70+
.orderBy(
71+
vote.id.asc()
72+
)
73+
.limit(size)
74+
.fetch();
75+
}
76+
77+
@Override
78+
public List<GetVoteOptionRes> getVoteOptionByVoteId(Long voteId) {
79+
return jpaQueryFactory
80+
.select(
81+
new QGetVoteOptionRes(
82+
voteOption.id,
83+
voteOption.optionText,
84+
voteRecord.id.count().intValue()
85+
)
86+
)
87+
.from(vote)
88+
.join(voteOption)
89+
.on(vote.voteTopicId.eq(voteOption.voteTopicId))
90+
.leftJoin(voteRecord)
91+
.on(voteRecord.voteId.eq(vote.id)
92+
.and(voteRecord.voteOptionId.eq(voteOption.id)))
93+
.where(
94+
vote.id.eq(voteId)
95+
)
96+
.groupBy(voteOption.id, voteOption.optionText)
97+
.orderBy(
98+
voteOption.id.asc()
99+
)
100+
.fetch();
101+
}
102+
103+
@Override
104+
public List<GetVoteOptionRes> getVoteOptionByVoteIdOrderByCount(Long voteId) {
105+
return jpaQueryFactory
106+
.select(
107+
new QGetVoteOptionRes(
108+
voteOption.id,
109+
voteOption.optionText,
110+
voteRecord.id.count().intValue()
111+
)
112+
)
113+
.from(vote)
114+
.join(voteOption)
115+
.on(vote.voteTopicId.eq(voteOption.voteTopicId))
116+
.leftJoin(voteRecord)
117+
.on(voteRecord.voteId.eq(vote.id)
118+
.and(voteRecord.voteOptionId.eq(voteOption.id)))
119+
.where(
120+
vote.id.eq(voteId)
121+
)
122+
.groupBy(voteOption.id, voteOption.optionText)
123+
.orderBy(
124+
voteRecord.id.count().intValue().asc()
125+
)
126+
.fetch();
127+
}
128+
129+
private BooleanExpression gtVoteCursor(Long lastVoteId) {
130+
if (lastVoteId== null) return null;
131+
return vote.id.gt(lastVoteId);
132+
}
48133
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package devkor.com.teamcback.domain.vote.repository;
22

33
import devkor.com.teamcback.domain.vote.entity.Vote;
4+
import devkor.com.teamcback.domain.vote.entity.VoteStatus;
45
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Query;
7+
import org.springframework.data.repository.query.Param;
58

9+
import java.util.List;
610
import java.util.Optional;
711

812
public interface VoteRepository extends JpaRepository<Vote, Long> {
913
Optional<Vote> findByVoteTopicIdAndPlaceId(Long voteTopicId, Long placeId);
14+
15+
@Query("SELECT v FROM Vote v WHERE v.status IS NULL OR v.status = :status")
16+
List<Vote> findAllByStatus(@Param("status")VoteStatus status);
1017
}

src/main/java/devkor/com/teamcback/domain/vote/service/VoteService.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
import devkor.com.teamcback.domain.vote.repository.VoteTopicRepository;
1515
import devkor.com.teamcback.global.annotation.UpdateScore;
1616
import devkor.com.teamcback.global.exception.exception.GlobalException;
17+
import devkor.com.teamcback.global.response.CursorPageRes;
1718
import lombok.RequiredArgsConstructor;
1819
import org.springframework.stereotype.Service;
1920
import org.springframework.transaction.annotation.Transactional;
2021

2122
import java.util.*;
2223

24+
import static devkor.com.teamcback.domain.vote.entity.VoteStatus.CLOSED;
2325
import static devkor.com.teamcback.global.response.ResultCode.*;
2426

2527
@Service
@@ -58,7 +60,7 @@ public GetVoteRes getVoteByPlace(Long voteTopicId, Long placeId) {
5860
/**
5961
* 투표 저장
6062
*/
61-
@UpdateScore(addScore = 2)
63+
@UpdateScore(addScore = 3)
6264
@Transactional
6365
public SaveVoteRecordRes saveVoteRecord(Long userId, SaveVoteRecordReq req) {
6466
if(userId == null) throw new GlobalException(FORBIDDEN);
@@ -87,18 +89,57 @@ else if(!Objects.equals(voteRecord.getVoteOptionId(), voteOption.getId())){ //
8789
voteRecord.setVoteOptionId(null); // 기존 투표 옵션을 null로 설정 (기록 남기기)
8890
}
8991

92+
// 투표 상태 변경
93+
List<GetVoteOptionRes> voteOptionList = voteOptionRepository.getVoteOptionByVoteIdOrderByCount(vote.getId()); // 투표 항목, 현황
94+
int min = voteOptionList.get(0).getVoteCount();
95+
int max = voteOptionList.get(voteOptionList.size() - 1).getVoteCount();
96+
if(max - min >= 30) vote.changeStatus(CLOSED);
97+
9098
return new SaveVoteRecordRes();
9199
}
92100

93101
/**
94-
* 투표 상태 변경(토글)
102+
* 투표 리스트 조회
103+
*/
104+
@Transactional(readOnly = true)
105+
public CursorPageRes<GetVoteRes> getVoteList(VoteStatus status, Long lastVoteId, int size) {
106+
107+
List<GetVoteRes> resList = new ArrayList<>();
108+
109+
List<Vote> voteList = voteOptionRepository.getVoteByStatusWithPage(status, lastVoteId, size + 1);
110+
111+
for(Vote vote : voteList) {
112+
// 투표 주제
113+
VoteTopic voteTopic = findVoteTopic(vote.getVoteTopicId());
114+
115+
// 투표 장소
116+
Place place = findPlace(vote.getPlaceId());
117+
118+
// 투표 항목, 현황
119+
List<GetVoteOptionRes> voteOptionList = voteOptionRepository.getVoteOptionByVoteId(vote.getId());
120+
121+
resList.add(new GetVoteRes(vote.getId(), vote.getStatus().toString(), voteTopic.getId(), voteTopic.getTopic(), place.getId(), place.getName(), voteOptionList));
122+
}
123+
124+
// CursorPageRes 응답
125+
boolean hasNext = voteList.size() > size;
126+
if(hasNext) {
127+
resList.remove(size);
128+
}
129+
Long lastCursorId = voteList.isEmpty() ? null : voteList.get(voteList.size() - 1).getId();
130+
131+
return new CursorPageRes<>(resList, hasNext, lastCursorId);
132+
}
133+
134+
/**
135+
* 투표 상태 변경
95136
*/
96-
public ChangeVoteStatusRes changeVoteStatus(Long voteId) {
137+
public ChangeVoteStatusRes changeVoteStatus(Long voteId, VoteStatus status) {
97138
// 투표
98139
Vote vote = findVote(voteId);
99140

100141
// 상태 변경
101-
vote.changeStatus();
142+
vote.changeStatus(status);
102143

103144
return new ChangeVoteStatusRes();
104145
}

0 commit comments

Comments
 (0)