Skip to content

Commit 3f35799

Browse files
Merge pull request #260 from prgrms-web-devcourse-final-project/refactor/EA3-183-time-vote
[EA3-183] refactor : 모임 일정 조율 관련 검증 로직 및 예외 응답 구조 리팩토링 (강사 피드백 반영)
2 parents d8a8d2e + 27f2357 commit 3f35799

23 files changed

+657
-487
lines changed

src/main/java/grep/neogulcoder/domain/timevote/TimeVote.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class TimeVote extends BaseEntity {
3131
@Column(nullable = false)
3232
private LocalDateTime timeSlot;
3333

34-
protected TimeVote() {};
34+
protected TimeVote() {}
3535

3636
@Builder
3737
public TimeVote(TimeVotePeriod period, Long studyMemberId, LocalDateTime timeSlot) {

src/main/java/grep/neogulcoder/domain/timevote/TimeVotePeriod.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ public class TimeVotePeriod extends BaseEntity {
3030
protected TimeVotePeriod() {}
3131

3232
@Builder
33-
public TimeVotePeriod(Long periodId, Long studyId, LocalDateTime startDate, LocalDateTime endDate) {
34-
this.periodId = periodId;
33+
public TimeVotePeriod(Long studyId, LocalDateTime startDate, LocalDateTime endDate) {
3534
this.studyId = studyId;
3635
this.startDate = startDate;
3736
this.endDate = endDate;

src/main/java/grep/neogulcoder/domain/timevote/TimeVoteStat.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import java.time.LocalDateTime;
1414
import lombok.Builder;
1515
import lombok.Getter;
16+
import lombok.extern.slf4j.Slf4j;
1617

18+
@Slf4j
1719
@Getter
1820
@Entity
1921
public class TimeVoteStat extends BaseEntity {
@@ -36,7 +38,7 @@ public class TimeVoteStat extends BaseEntity {
3638
@Column(nullable = false)
3739
private Long version;
3840

39-
protected TimeVoteStat() {};
41+
protected TimeVoteStat() {}
4042

4143
@Builder
4244
public TimeVoteStat(TimeVotePeriod period, LocalDateTime timeSlot, Long voteCount, Long version) {
@@ -56,6 +58,7 @@ public static TimeVoteStat of(TimeVotePeriod period, LocalDateTime timeSlot, Lon
5658
}
5759

5860
public void addVotes(Long countToAdd) {
61+
log.debug("addVotes: 이전 voteCount={}, 추가 count={}, 이전 version={}", this.voteCount, countToAdd, this.version);
5962
this.voteCount += countToAdd;
6063
}
6164
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package grep.neogulcoder.domain.timevote.context;
2+
3+
import grep.neogulcoder.domain.study.StudyMember;
4+
import grep.neogulcoder.domain.timevote.TimeVotePeriod;
5+
6+
public record TimeVoteContext(
7+
StudyMember studyMember,
8+
TimeVotePeriod period
9+
) {}

src/main/java/grep/neogulcoder/domain/timevote/controller/TimeVoteController.java

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77
import grep.neogulcoder.domain.timevote.dto.response.TimeVoteResponse;
88
import grep.neogulcoder.domain.timevote.dto.response.TimeVoteStatResponse;
99
import grep.neogulcoder.domain.timevote.dto.response.TimeVoteSubmissionStatusResponse;
10-
import grep.neogulcoder.domain.timevote.service.TimeVotePeriodService;
11-
import grep.neogulcoder.domain.timevote.service.TimeVoteService;
12-
import grep.neogulcoder.domain.timevote.service.TimeVoteStatService;
10+
import grep.neogulcoder.domain.timevote.service.period.TimeVotePeriodService;
11+
import grep.neogulcoder.domain.timevote.service.stat.TimeVoteStatService;
12+
import grep.neogulcoder.domain.timevote.service.vote.TimeVoteService;
1313
import grep.neogulcoder.global.auth.Principal;
1414
import grep.neogulcoder.global.response.ApiResponse;
1515
import jakarta.validation.Valid;
1616
import java.util.List;
1717
import lombok.RequiredArgsConstructor;
18+
import org.springframework.http.HttpStatus;
19+
import org.springframework.http.ResponseEntity;
1820
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1921
import org.springframework.web.bind.annotation.DeleteMapping;
2022
import org.springframework.web.bind.annotation.GetMapping;
@@ -35,68 +37,74 @@ public class TimeVoteController implements TimeVoteSpecification {
3537
private final TimeVoteStatService timeVoteStatService;
3638

3739
@PostMapping("/periods")
38-
public ApiResponse<TimeVotePeriodResponse> createPeriod(
40+
public ResponseEntity<ApiResponse<TimeVotePeriodResponse>> createPeriod(
3941
@PathVariable("studyId") Long studyId,
4042
@RequestBody @Valid TimeVotePeriodCreateRequest request,
4143
@AuthenticationPrincipal Principal userDetails
4244
) {
43-
TimeVotePeriodResponse response = timeVotePeriodService.createTimeVotePeriodAndReturn(request, studyId, userDetails.getUserId());
44-
return ApiResponse.success(response);
45+
TimeVotePeriodResponse response = timeVotePeriodService.createTimeVotePeriodAndReturn(
46+
request, studyId, userDetails.getUserId()
47+
);
48+
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
4549
}
4650

4751
@GetMapping("/votes")
48-
public ApiResponse<TimeVoteResponse> getMyVotes(
52+
public ResponseEntity<ApiResponse<TimeVoteResponse>> getMyVotes(
4953
@PathVariable("studyId") Long studyId,
5054
@AuthenticationPrincipal Principal userDetails
5155
) {
5256
TimeVoteResponse response = timeVoteService.getMyVotes(studyId, userDetails.getUserId());
53-
return ApiResponse.success(response);
57+
return ResponseEntity.ok(ApiResponse.success(response));
5458
}
5559

5660
@PostMapping("/votes")
57-
public ApiResponse<TimeVoteResponse> submitVote(
61+
public ResponseEntity<ApiResponse<TimeVoteResponse>> submitVote(
5862
@PathVariable("studyId") Long studyId,
5963
@RequestBody @Valid TimeVoteCreateRequest request,
6064
@AuthenticationPrincipal Principal userDetails
6165
) {
62-
TimeVoteResponse response = timeVoteService.submitVotes(request, studyId, userDetails.getUserId());
63-
return ApiResponse.success(response);
66+
TimeVoteResponse response = timeVoteService.submitVotes(request, studyId,
67+
userDetails.getUserId());
68+
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
6469
}
6570

6671
@PutMapping("/votes")
67-
public ApiResponse<TimeVoteResponse> updateVote(
72+
public ResponseEntity<ApiResponse<TimeVoteResponse>> updateVote(
6873
@PathVariable("studyId") Long studyId,
6974
@RequestBody @Valid TimeVoteUpdateRequest request,
7075
@AuthenticationPrincipal Principal userDetails
7176
) {
72-
TimeVoteResponse response = timeVoteService.updateVotes(request, studyId, userDetails.getUserId());
73-
return ApiResponse.success(response);
77+
TimeVoteResponse response = timeVoteService.updateVotes(request, studyId,
78+
userDetails.getUserId());
79+
return ResponseEntity.ok(ApiResponse.success(response));
7480
}
7581

7682
@DeleteMapping("/votes")
77-
public ApiResponse<Void> deleteAllVotes(
83+
public ResponseEntity<ApiResponse<Void>> deleteAllVotes(
7884
@PathVariable("studyId") Long studyId,
7985
@AuthenticationPrincipal Principal userDetails
8086
) {
8187
timeVoteService.deleteAllVotes(studyId, userDetails.getUserId());
82-
return ApiResponse.success("성공적으로 투표를 삭제했습니다.");
88+
return ResponseEntity.ok(ApiResponse.success("성공적으로 투표를 삭제했습니다."));
8389
}
8490

8591
@GetMapping("/periods/submissions")
86-
public ApiResponse<List<TimeVoteSubmissionStatusResponse>> getSubmissionStatusList(
92+
public ResponseEntity<ApiResponse<List<TimeVoteSubmissionStatusResponse>>> getSubmissionStatusList(
8793
@PathVariable("studyId") Long studyId,
8894
@AuthenticationPrincipal Principal userDetails
8995
) {
90-
List<TimeVoteSubmissionStatusResponse> statuses = timeVoteService.getSubmissionStatusList(studyId, userDetails.getUserId());
91-
return ApiResponse.success(statuses);
96+
List<TimeVoteSubmissionStatusResponse> statuses = timeVoteService.getSubmissionStatusList(
97+
studyId, userDetails.getUserId()
98+
);
99+
return ResponseEntity.ok(ApiResponse.success(statuses));
92100
}
93101

94102
@GetMapping("/periods/stats")
95-
public ApiResponse<TimeVoteStatResponse> getVoteStats(
103+
public ResponseEntity<ApiResponse<TimeVoteStatResponse>> getVoteStats(
96104
@PathVariable("studyId") Long studyId,
97105
@AuthenticationPrincipal Principal userDetails
98106
) {
99-
TimeVoteStatResponse response = timeVoteStatService.getStats(studyId, userDetails.getUserId());
100-
return ApiResponse.success(response);
107+
TimeVoteStatResponse response = timeVoteStatService.getStats(studyId, userDetails.getUserId());
108+
return ResponseEntity.ok(ApiResponse.success(response));
101109
}
102110
}

src/main/java/grep/neogulcoder/domain/timevote/controller/TimeVoteSpecification.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,52 +9,53 @@
99
import io.swagger.v3.oas.annotations.tags.Tag;
1010
import jakarta.validation.Valid;
1111
import java.util.List;
12+
import org.springframework.http.ResponseEntity;
1213
import org.springframework.web.bind.annotation.RequestBody;
1314

1415
@Tag(name = "Time-Vote", description = "스터디 모임 시간 조율 API")
1516
public interface TimeVoteSpecification {
1617

1718
@Operation(summary = "스터디 모임 일정 투표 기간 생성", description = "팀장이 가능한 시간 요청을 생성합니다.")
18-
ApiResponse<TimeVotePeriodResponse> createPeriod(
19+
ResponseEntity<ApiResponse<TimeVotePeriodResponse>> createPeriod(
1920
@Parameter(description = "스터디 ID", example = "6") Long studyId,
2021
@RequestBody @Valid TimeVotePeriodCreateRequest request,
2122
Principal userDetails
2223
);
2324

2425
@Operation(summary = "사용자가 제출한 시간 목록 조회", description = "해당 사용자가 이전에 제출한 시간대 목록을 조회합니다.")
25-
ApiResponse<TimeVoteResponse> getMyVotes(
26+
ResponseEntity<ApiResponse<TimeVoteResponse>> getMyVotes(
2627
@Parameter(description = "스터디 ID", example = "6") Long studyId,
2728
Principal userDetails
2829
);
2930

3031
@Operation(summary = "사용자 가능 시간대 제출", description = "스터디 멤버가 가능 시간을 제출합니다.")
31-
ApiResponse<TimeVoteResponse> submitVote(
32+
ResponseEntity<ApiResponse<TimeVoteResponse>> submitVote(
3233
@Parameter(description = "스터디 ID", example = "6") Long studyId,
3334
@RequestBody @Valid TimeVoteCreateRequest request,
3435
Principal userDetails
3536
);
3637

3738
@Operation(summary = "사용자 시간대 수정", description = "사용자가 기존에 제출한 시간을 수정합니다.")
38-
ApiResponse<TimeVoteResponse> updateVote(
39+
ResponseEntity<ApiResponse<TimeVoteResponse>> updateVote(
3940
@Parameter(description = "스터디 ID", example = "6") Long studyId,
4041
@RequestBody @Valid TimeVoteUpdateRequest request,
4142
Principal userDetails
4243
);
4344

4445
@Operation(summary = "사용자 전체 시간 삭제", description = "사용자가 제출한 시간 전체를 삭제합니다.")
45-
ApiResponse<Void> deleteAllVotes(
46+
ResponseEntity<ApiResponse<Void>> deleteAllVotes(
4647
@Parameter(description = "스터디 ID", example = "6") Long studyId,
4748
Principal userDetails
4849
);
4950

5051
@Operation(summary = "투표 통계 조회", description = "투표 기간의 시간대별 통계 정보를 조회합니다.")
51-
ApiResponse<TimeVoteStatResponse> getVoteStats(
52+
ResponseEntity<ApiResponse<TimeVoteStatResponse>> getVoteStats(
5253
@Parameter(description = "스터디 ID", example = "6") Long studyId,
5354
Principal userDetails
5455
);
5556

5657
@Operation(summary = "사용자별 제출 여부 조회", description = "특정 스터디의 모든 멤버별 시간 제출 여부를 반환합니다.")
57-
ApiResponse<List<TimeVoteSubmissionStatusResponse>> getSubmissionStatusList(
58+
ResponseEntity<ApiResponse<List<TimeVoteSubmissionStatusResponse>>> getSubmissionStatusList(
5859
@Parameter(description = "스터디 ID", example = "6") Long studyId,
5960
Principal userDetails
6061
);
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
package grep.neogulcoder.domain.timevote.dto.request;
22

3-
import grep.neogulcoder.domain.timevote.TimeVote;
4-
import grep.neogulcoder.domain.timevote.TimeVotePeriod;
53
import io.swagger.v3.oas.annotations.media.Schema;
64
import jakarta.validation.constraints.NotEmpty;
75
import java.time.LocalDateTime;
86
import java.util.List;
9-
import java.util.stream.Collectors;
107
import lombok.Builder;
118
import lombok.Getter;
129

1310
@Getter
1411
@Schema(description = "스터디 모임 일정 조율 - 가능 시간 제출 요청 DTO")
1512
public class TimeVoteCreateRequest {
1613

17-
@NotEmpty
14+
@NotEmpty(message = "시간대를 1개 이상 선택해주세요.")
1815
@Schema(
1916
description = "시간대 리스트",
2017
example = "[\"2025-07-25T10:00:00\", \"2025-07-26T11:00:00\"]"
@@ -27,14 +24,4 @@ private TimeVoteCreateRequest() {}
2724
private TimeVoteCreateRequest(List<LocalDateTime> timeSlots) {
2825
this.timeSlots = timeSlots;
2926
}
30-
31-
public List<TimeVote> toEntities(TimeVotePeriod period, Long studyMemberId) {
32-
return timeSlots.stream()
33-
.map(slot -> TimeVote.builder()
34-
.period(period)
35-
.studyMemberId(studyMemberId)
36-
.timeSlot(slot)
37-
.build())
38-
.collect(Collectors.toList());
39-
}
4027
}
Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package grep.neogulcoder.domain.timevote.dto.request;
22

3-
import grep.neogulcoder.domain.timevote.TimeVotePeriod;
43
import io.swagger.v3.oas.annotations.media.Schema;
54
import jakarta.validation.constraints.NotNull;
65
import java.time.LocalDateTime;
@@ -11,11 +10,11 @@
1110
@Schema(description = "스터디 모임 일정 조율 - 팀장이 가능 시간 요청을 생성할 때 사용하는 요청 DTO")
1211
public class TimeVotePeriodCreateRequest {
1312

14-
@NotNull
13+
@NotNull(message = "시작일은 필수입니다.")
1514
@Schema(description = "시작일", example = "2025-07-25T00:00:00")
1615
private LocalDateTime startDate;
1716

18-
@NotNull
17+
@NotNull(message = "종료일은 필수입니다.")
1918
@Schema(description = "종료일", example = "2025-07-30T23:59:59")
2019
private LocalDateTime endDate;
2120

@@ -26,16 +25,4 @@ private TimeVotePeriodCreateRequest(LocalDateTime startDate, LocalDateTime endDa
2625
this.startDate = startDate;
2726
this.endDate = endDate;
2827
}
29-
30-
public TimeVotePeriod toEntity(Long studyId, LocalDateTime adjustedEndDate) {
31-
return TimeVotePeriod.builder()
32-
.startDate(this.startDate)
33-
.endDate(adjustedEndDate)
34-
.studyId(studyId)
35-
.build();
36-
}
37-
38-
public static TimeVotePeriodCreateRequest of(LocalDateTime startDate, LocalDateTime endDate) {
39-
return new TimeVotePeriodCreateRequest(startDate, endDate);
40-
}
4128
}
Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
package grep.neogulcoder.domain.timevote.dto.request;
22

3-
import grep.neogulcoder.domain.timevote.TimeVote;
4-
import grep.neogulcoder.domain.timevote.TimeVotePeriod;
53
import io.swagger.v3.oas.annotations.media.Schema;
64
import jakarta.validation.constraints.NotEmpty;
75
import java.time.LocalDateTime;
86
import java.util.List;
9-
import java.util.stream.Collectors;
107
import lombok.Builder;
118
import lombok.Getter;
129

1310
@Getter
1411
@Schema(description = "스터디 모임 일정 조율 - 가능 시간 수정 요청 DTO")
1512
public class TimeVoteUpdateRequest {
1613

17-
@NotEmpty
14+
@NotEmpty(message = "시간대를 1개 이상 선택해주세요.")
1815
@Schema(
1916
description = "시간대 리스트",
2017
example = "[\"2025-07-27T10:00:00\", \"2025-07-28T11:00:00\"]"
@@ -27,14 +24,4 @@ private TimeVoteUpdateRequest() {}
2724
private TimeVoteUpdateRequest( List<LocalDateTime> timeSlots) {
2825
this.timeSlots = timeSlots;
2926
}
30-
31-
public List<TimeVote> toEntities(TimeVotePeriod period, Long studyMemberId) {
32-
return timeSlots.stream()
33-
.map(slot -> TimeVote.builder()
34-
.period(period)
35-
.studyMemberId(studyMemberId)
36-
.timeSlot(slot)
37-
.build())
38-
.collect(Collectors.toList());
39-
}
4027
}

src/main/java/grep/neogulcoder/domain/timevote/dto/response/TimeVotePeriodResponse.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,3 @@ public static TimeVotePeriodResponse from(TimeVotePeriod timeVotePeriod) {
3434
.build();
3535
}
3636
}
37-

0 commit comments

Comments
 (0)