From 75e4cddf6fa591522184c799c9aea306a081e93d Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:04:05 +0900 Subject: [PATCH 01/33] =?UTF-8?q?refactor(#33):=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20Input=20?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20scheduleId=20=EB=B0=9B=EC=95=84=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EA=B2=83=20PathVariable=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleController.java | 21 +++++++--------- .../controller/ScheduleControllerDocs.java | 2 +- .../schedule/dto/ScheduleUpdateInput.java | 13 ++-------- .../moim/schedule/entity/Schedule.java | 24 ++++++++++++++----- .../schedule/service/ScheduleService.java | 18 ++++++-------- .../schedule/service/ScheduleServiceTest.java | 4 ++-- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 04dbefb..2a308c5 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -1,12 +1,7 @@ package com.example.moim.schedule.controller; +import com.example.moim.schedule.dto.*; import com.example.moim.schedule.service.ScheduleService; -import com.example.moim.schedule.dto.ScheduleDetailOutput; -import com.example.moim.schedule.dto.ScheduleInput; -import com.example.moim.schedule.dto.ScheduleOutput; -import com.example.moim.schedule.dto.ScheduleSearchInput; -import com.example.moim.schedule.dto.ScheduleUpdateInput; -import com.example.moim.schedule.dto.ScheduleVoteInput; import com.example.moim.user.dto.UserDetailsImpl; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -26,9 +21,9 @@ public ScheduleOutput scheduleSave(@RequestBody @Valid ScheduleInput scheduleInp return scheduleService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); } - @PatchMapping("/schedule") - public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.updateSchedule(scheduleUpdateInput, userDetailsImpl.getUser()); + @PatchMapping("/schedule/{scheduleId}") + public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("scheduleId") Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + return scheduleService.updateSchedule(scheduleUpdateInput, scheduleId, userDetailsImpl.getUser()); } @GetMapping(value = "/schedule", produces = MediaType.APPLICATION_JSON_VALUE) @@ -66,8 +61,8 @@ public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDe scheduleService.closeSchedule(id, userDetailsImpl.getUser()); } -// @PostMapping("/schedule/comment") -// public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { -// scheduleService.saveComment(commentInput, userDetailsImpl.getUser()); -// } + @PostMapping("/schedule/comment") + public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + scheduleService.saveComment(commentInput, userDetailsImpl.getUser()); + } } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index acf241d..d99aef0 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -22,7 +22,7 @@ public interface ScheduleControllerDocs { ScheduleOutput scheduleSave(@RequestBody ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 수정", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") - ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "한달 일정 조회", description = "쿼리파라미터 예시: /schedule?date=202404&clubId=6&search=친선 경기&category=친선 경기") List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput); diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java index 2b3a5ac..b85ca33 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java @@ -13,29 +13,20 @@ @Data public class ScheduleUpdateInput { private Long clubId; - private Long id; - @NotBlank(message = "일정 제목을 입력해주세요.") private String title; - @NotBlank(message = "일정 장소를 입력해주세요.") private String location; - @Schema(pattern = "yyyy-MM-dd HH:mm") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotNull(message = "일정 시작 시간을 입력해주세요.") private LocalDateTime startTime; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotNull(message = "일정 종료 시간을 입력해주세요.") private LocalDateTime endTime; @Min(value = 1, message = "참여 인원은 1명 이상이어야 합니다") - @NotNull(message = "참여 인원을 입력해주세요.") - private int minPeople;//참여인원수 - @NotBlank(message = "일정 카테고리를 입력해 주세요.") + private Integer minPeople; private String category; private String note; @Builder - public ScheduleUpdateInput(Long clubId, Long id, String title, String location, @NotNull(message = "일정 시작 시간을 입력해주세요.") LocalDateTime startTime, @NotNull(message = "일정 종료 시간을 입력해주세요.") LocalDateTime endTime, @NotNull(message = "참여 인원을 입력해주세요.") int minPeople, String category, String note) { + public ScheduleUpdateInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, @NotNull(message = "참여 인원을 입력해주세요.") int minPeople, String category, String note) { this.clubId = clubId; - this.id = id; this.title = title; this.location = location; this.startTime = startTime; diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index c26ca65..e20f0ab 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -75,12 +75,24 @@ public void reVote(String originalAttendance, String attendance) { } public void updateSchedule(ScheduleUpdateInput scheduleUpdateInput) { - this.title = scheduleUpdateInput.getTitle(); - this.location = scheduleUpdateInput.getLocation(); - this.startTime = scheduleUpdateInput.getStartTime(); - this.endTime = scheduleUpdateInput.getEndTime(); - this.minPeople = scheduleUpdateInput.getMinPeople(); - this.category = scheduleUpdateInput.getCategory(); + if (scheduleUpdateInput.getTitle() != null) { + this.title = scheduleUpdateInput.getTitle(); + } + if (scheduleUpdateInput.getLocation() != null) { + this.location = scheduleUpdateInput.getLocation(); + } + if (scheduleUpdateInput.getStartTime() != null) { + this.startTime = scheduleUpdateInput.getStartTime(); + } + if (scheduleUpdateInput.getEndTime() != null) { + this.endTime = scheduleUpdateInput.getEndTime(); + } + if (scheduleUpdateInput.getMinPeople() != null) { + this.minPeople = scheduleUpdateInput.getMinPeople(); + } + if (scheduleUpdateInput.getCategory() != null) { + this.category = scheduleUpdateInput.getCategory(); + } if (scheduleUpdateInput.getNote() != null) { this.note = scheduleUpdateInput.getNote(); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java index d46f48b..aa48485 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleService.java @@ -8,12 +8,8 @@ import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; -import com.example.moim.schedule.dto.ScheduleDetailOutput; -import com.example.moim.schedule.dto.ScheduleInput; -import com.example.moim.schedule.dto.ScheduleOutput; -import com.example.moim.schedule.dto.ScheduleSearchInput; -import com.example.moim.schedule.dto.ScheduleUpdateInput; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.dto.*; +import com.example.moim.schedule.entity.Comment; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.entity.ScheduleVote; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; @@ -59,13 +55,13 @@ public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { } @Transactional - public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, User user) { + public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long scheduleId, User user) { UserClub userClub = userClubRepository.findByClubAndUser(clubRepository.findById(scheduleUpdateInput.getClubId()).get(), user).get(); if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } - Schedule schedule = scheduleRepository.findById(scheduleUpdateInput.getId()).get(); + Schedule schedule = scheduleRepository.findById(scheduleId).get(); schedule.updateSchedule(scheduleUpdateInput); return new ScheduleOutput(schedule); } @@ -156,7 +152,7 @@ public void closeSchedule(Long id, User user) { schedule.closeSchedule(); } -// public void saveComment(CommentInput commentInput, User user) { -// commentRepository.save(Comment.createComment(user, scheduleRepository.findById(commentInput.getId()).get(), commentInput.getContents())); -// } + public void saveComment(CommentInput commentInput, User user) { + commentRepository.save(Comment.createComment(user, scheduleRepository.findById(commentInput.getId()).get(), commentInput.getContents())); + } } diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java index 47776fa..674fdb2 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java @@ -138,7 +138,7 @@ void updateSchedule() { when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - ScheduleOutput scheduleOutput = scheduleService.updateSchedule(scheduleUpdateInput, user); + ScheduleOutput scheduleOutput = scheduleService.updateSchedule(scheduleUpdateInput, 1L, user); //then assertThat(scheduleOutput.getTitle()).isEqualTo("update title"); assertThat(scheduleOutput.getLocation()).isEqualTo("update location"); @@ -159,7 +159,7 @@ void updateSchedule_wrong_permission() { when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); //then Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.updateSchedule(scheduleUpdateInput, user); + scheduleService.updateSchedule(scheduleUpdateInput, 1L, user); }); assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); From a16b16ba79d59327fd36ec36019db9319fe048c5 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:11:34 +0900 Subject: [PATCH 02/33] =?UTF-8?q?refactor(#33):=20=EC=A0=95=EC=A0=81=20?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A6=AC=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=A0=81=EC=9A=A9(createSchedule?= =?UTF-8?q?=20->=20from)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/match/service/MatchService.java | 5 ++--- .../moim/schedule/entity/Schedule.java | 6 +++--- .../schedule/service/ScheduleService.java | 14 ++++++++----- .../schedule/service/ScheduleServiceTest.java | 21 +++++++++---------- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/example/moim/match/service/MatchService.java b/src/main/java/com/example/moim/match/service/MatchService.java index 754e7b0..76a1cae 100644 --- a/src/main/java/com/example/moim/match/service/MatchService.java +++ b/src/main/java/com/example/moim/match/service/MatchService.java @@ -1,7 +1,6 @@ package com.example.moim.match.service; import com.example.moim.club.entity.Club; -import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.entity.*; import com.example.moim.match.exception.advice.MatchControllerAdvice; @@ -90,7 +89,7 @@ public MatchOutput saveMatch(User user, MatchInput matchInput) { .orElseThrow(() -> new MatchControllerAdvice(ResponseCode.CLUB_NOT_FOUND)), matchInput)); //일정에 매치 등록 - Schedule schedule = scheduleRepository.save(Schedule.createSchedule(clubRepository.findById(matchInput.getClubId()) + Schedule schedule = scheduleRepository.save(Schedule.from(clubRepository.findById(matchInput.getClubId()) .orElseThrow(() -> new MatchControllerAdvice(ResponseCode.CLUB_NOT_FOUND)), match.createScheduleFromMatch())); match.setSchedule(schedule); matchRepository.save(match); @@ -186,7 +185,7 @@ public MatchApplyOutput saveMatchApp(User user, Long matchId, Long clubId) { matchRepository.findMatchByClub(club).forEach(m -> m.timeDuplicationCheck(match.getStartTime(), match.getEndTime())); MatchApplication matchApplication = matchApplicationRepository.save(MatchApplication.applyMatch(match, club)); - Schedule schedule = scheduleRepository.save(Schedule.createSchedule(matchApplication.getClub(), matchApplication.getMatch().createScheduleFromMatch())); + Schedule schedule = scheduleRepository.save(Schedule.from(matchApplication.getClub(), matchApplication.getMatch().createScheduleFromMatch())); matchApplication.setSchedule(schedule); matchApplicationRepository.save(matchApplication); diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index e20f0ab..480be61 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -35,7 +35,7 @@ public class Schedule extends BaseEntity { @OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE) private List comment = new ArrayList<>(); - public static Schedule createSchedule(Club club, ScheduleInput scheduleInput) { + public static Schedule from(Club club, ScheduleInput scheduleInput) { Schedule schedule = new Schedule(); schedule.club = club; schedule.title = scheduleInput.getTitle(); @@ -74,7 +74,7 @@ public void reVote(String originalAttendance, String attendance) { } } - public void updateSchedule(ScheduleUpdateInput scheduleUpdateInput) { + public void update(ScheduleUpdateInput scheduleUpdateInput) { if (scheduleUpdateInput.getTitle() != null) { this.title = scheduleUpdateInput.getTitle(); } @@ -98,7 +98,7 @@ public void updateSchedule(ScheduleUpdateInput scheduleUpdateInput) { } } - public void closeSchedule() { + public void close() { this.isClose = true; } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java index aa48485..4331a4e 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleService.java @@ -42,15 +42,19 @@ public class ScheduleService { private final MatchApplicationRepository matchApplicationRepository; public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { - UserClub userClub = userClubRepository.findByClubAndUser(clubRepository.findById(scheduleInput.getClubId()).get(), user).get(); + Club club = clubRepository.findById(scheduleInput.getClubId()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); + UserClub userClub = userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } - Schedule schedule = scheduleRepository.save( - Schedule.createSchedule(clubRepository.findById(scheduleInput.getClubId()).get(), scheduleInput)); + Schedule schedule = scheduleRepository.save(Schedule.from(club, scheduleInput)); + /** + * TODO: 알림 리팩터링 버전으로 다시 적용해야 함 + */ eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); + return new ScheduleOutput(schedule); } @@ -62,7 +66,7 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo } Schedule schedule = scheduleRepository.findById(scheduleId).get(); - schedule.updateSchedule(scheduleUpdateInput); + schedule.update(scheduleUpdateInput); return new ScheduleOutput(schedule); } @@ -149,7 +153,7 @@ public void closeSchedule(Long id, User user) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } - schedule.closeSchedule(); + schedule.close(); } public void saveComment(CommentInput commentInput, User user) { diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java index 674fdb2..f4572a2 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java @@ -2,7 +2,6 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.entity.*; -import com.example.moim.club.exception.advice.ClubControllerAdvice; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; @@ -89,7 +88,7 @@ void init() { void saveSchedule() { //given Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); User user = User.createUser(signupInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); //when @@ -133,7 +132,7 @@ void updateSchedule() { Club club = Club.createClub(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); //when when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); @@ -171,7 +170,7 @@ void updateSchedule_wrong_permission() { void findSchedule() { //given Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); //when @@ -211,7 +210,7 @@ void findSchedule_zero_schedule() { void findDaySchedule() { //given Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); //when @@ -234,7 +233,7 @@ void findScheduleDetail() { //given Club club = Club.createClub(clubInput, null); MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); schedule.setCreatedDate(); schedule.setUpdatedDate(); //when @@ -254,7 +253,7 @@ void findScheduleDetail() { void voteSchedule_re() { //given Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "true"); @@ -273,7 +272,7 @@ void voteSchedule_re() { void voteSchedule() { //given Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "false"); @@ -307,7 +306,7 @@ void voteEncourage() { Long id = 1L; Club club = Club.createClub(clubInput, null); User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); //when when(scheduleRepository.findScheduleById(any(Long.class))).thenReturn(schedule); @@ -325,7 +324,7 @@ void closeSchedule() { //given Club club = Club.createClub(clubInput, null); User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); @@ -342,7 +341,7 @@ void closeSchedule_wrong_permission() { //given Club club = Club.createClub(clubInput, null); User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); + Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createUserClub(user, club); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); From 9e9197febb1ebaa7adada184a53745a21ec750a0 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:14:15 +0900 Subject: [PATCH 03/33] =?UTF-8?q?refactor(#33):=20Club=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=ED=95=A8=EC=88=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Club=20=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/moim/club/entity/Club.java | 12 +++----- .../club/service/ClubCommandServiceImpl.java | 8 ++--- .../repository/ClubRepositoryImplTest.java | 4 +-- .../service/ClubCommandServiceImplTest.java | 30 +++++++++---------- .../service/ClubQueryServiceImplTest.java | 7 ++--- .../service/NoticeCommandServiceImplTest.java | 4 +-- .../service/NoticeQueryServiceImplTest.java | 4 +-- .../example/moim/match/MatchServiceTest.java | 4 +-- .../schedule/service/ScheduleServiceTest.java | 26 ++++++++-------- 9 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/example/moim/club/entity/Club.java b/src/main/java/com/example/moim/club/entity/Club.java index f175777..5661d1c 100644 --- a/src/main/java/com/example/moim/club/entity/Club.java +++ b/src/main/java/com/example/moim/club/entity/Club.java @@ -6,7 +6,6 @@ import com.example.moim.global.entity.BaseEntity; import com.example.moim.global.enums.*; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.global.util.TextUtils; import com.example.moim.match.entity.Match; import jakarta.persistence.*; import lombok.Getter; @@ -61,10 +60,7 @@ public class Club extends BaseEntity { @OneToMany(mappedBy = "club", cascade = CascadeType.REMOVE) private List notices = new ArrayList<>(); - /** - * TODO : university 는 없을 수도 있으므로, null 일 경우를 처리해주기 - */ - public static Club createClub(ClubInput clubInput, String profileImgPath) { + public static Club from(ClubInput clubInput, String profileImgPath) { Club club = new Club(); club.title = clubInput.getTitle(); club.explanation = clubInput.getExplanation(); @@ -83,7 +79,7 @@ public static Club createClub(ClubInput clubInput, String profileImgPath) { return club; } - public Club updateClubSearch(ClubSearch clubSearch) { + public Club updateSearch(ClubSearch clubSearch) { this.clubSearch = clubSearch; return this; } @@ -96,7 +92,7 @@ public void plusMemberCount() { memberCount++; } - public void updateClub(ClubUpdateInput clubUpdateInput, String profileImgPath) { + public void update(ClubUpdateInput clubUpdateInput, String profileImgPath) { if (StringUtils.hasText(clubUpdateInput.getTitle())) { this.title = clubUpdateInput.getTitle(); } @@ -132,7 +128,7 @@ public void updateClub(ClubUpdateInput clubUpdateInput, String profileImgPath) { } } - public void updateClubPassword(String newPassword) { + public void updatePassword(String newPassword) { this.clubPassword = newPassword; } } diff --git a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java index 7dadc51..7b87cde 100644 --- a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java @@ -41,7 +41,7 @@ public class ClubCommandServiceImpl implements ClubCommandService { @Transactional public ClubSaveOutput saveClub(User user, ClubInput clubInput) throws IOException { - Club club = clubRepository.save(Club.createClub(clubInput, fileService.upload(clubInput.getProfileImg(), "/club-profile"))); + Club club = clubRepository.save(Club.from(clubInput, fileService.upload(clubInput.getProfileImg(), "/club-profile"))); // 검색을 위한 저장 saveClubSearch(club); @@ -64,7 +64,7 @@ public ClubUpdateOutput updateClub(User user, ClubUpdateInput clubUpdateInput, L throw new ClubControllerAdvice(ResponseCode.CLUB_PASSWORD_INCORRECT); } - club.updateClub(clubUpdateInput, fileService.upload(clubUpdateInput.getProfileImg(), "/club-profile")); + club.update(clubUpdateInput, fileService.upload(clubUpdateInput.getProfileImg(), "/club-profile")); // 검색 정보 동기화를 위한 처리 club.getClubSearch().updateFrom(club); List userList = userClubRepository.findAllByClub(club).stream().map(UserClubOutput::new).toList(); @@ -124,7 +124,7 @@ public void clubPasswordUpdate(User user, ClubPswdUpdateInput clubPswdUpdateInpu if (!clubPswdUpdateInput.getNewPassword().equals(clubPswdUpdateInput.getRePassword())) { throw new ClubControllerAdvice(ResponseCode.CLUB_CHECK_PASSWORD_INCORRECT); } - club.updateClubPassword(clubPswdUpdateInput.getNewPassword()); + club.updatePassword(clubPswdUpdateInput.getNewPassword()); } private Club getClub(Long clubId) { @@ -140,7 +140,7 @@ private void saveClubSearch(Club club) { .allFieldsConcat(TextUtils.concatClean("|", club.getTitle(), club.getIntroduction(), club.getExplanation())) .build(); - club.updateClubSearch(clubSearchRepository.save(clubSearch)); + club.updateSearch(clubSearchRepository.save(clubSearch)); } // @Transactional diff --git a/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java b/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java index 2133b77..f2ba1a1 100644 --- a/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java +++ b/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java @@ -55,8 +55,8 @@ void init() { .university(university).gender(gender.getKoreanName()).activityArea(activityArea.getKoreanName()).ageRange(ageRange.getKoreanName()).sportsType(sportsType.getKoreanName()) .clubPassword(clubPassword).profileImg(profileImg).mainUniformColor(mainUniformColor).subUniformColor(subUniformColor).build(); - Club savedClub = clubRepository.save(Club.createClub(clubInput, "/club")); - Club savedClub2 = clubRepository.save(Club.createClub(clubInput2, "/club")); + Club savedClub = clubRepository.save(Club.from(clubInput, "/club")); + Club savedClub2 = clubRepository.save(Club.from(clubInput2, "/club")); ClubSearch clubSearch = ClubSearch.builder() .club(savedClub) diff --git a/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java b/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java index 42626bf..07ae3d3 100644 --- a/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java @@ -1,7 +1,6 @@ package com.example.moim.club.service; import com.example.moim.club.dto.request.*; -import com.example.moim.club.dto.response.ClubOutput; import com.example.moim.club.dto.response.ClubSaveOutput; import com.example.moim.club.dto.response.ClubUpdateOutput; import com.example.moim.club.dto.response.UserClubOutput; @@ -28,7 +27,6 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.util.List; import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -90,10 +88,10 @@ void init() { @DisplayName("새로운 동아리를 저장할 수 있다") void saveClub() throws IOException { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); UserClub userClub = UserClub.createLeaderUserClub(new User(), club); ClubSearch clubSearch = ClubSearch.builder().build(); - club.updateClubSearch(clubSearch); + club.updateSearch(clubSearch); //when when(clubRepository.save(any(Club.class))).thenReturn(club); when(userClubRepository.save(any(UserClub.class))).thenReturn(userClub); @@ -113,10 +111,10 @@ void saveClub() throws IOException { @DisplayName("동아리 정보를 업데이트 할 수 있다") void updateClub() throws IOException { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); UserClub userClub = UserClub.createLeaderUserClub(new User(), club); ClubSearch clubSearch = ClubSearch.builder().build(); - club.updateClubSearch(clubSearch); + club.updateSearch(clubSearch); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); @@ -135,7 +133,7 @@ void updateClub() throws IOException { @DisplayName("동아리 정보를 업데이트 할 때 비밀번호가 틀리면 예외가 발생한다") void updateClub_exception_wrong_password() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); UserClub userClub = UserClub.createLeaderUserClub(new User(), club); //when //then @@ -153,7 +151,7 @@ void updateClub_exception_wrong_password() { @DisplayName("동아리 정보를 업데이트 할 때 권한이 없으면 예외가 발생한다") void updateClub_exception_wrong_auth() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); UserClub userClub = UserClub.createUserClub(new User(), club); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); @@ -171,7 +169,7 @@ void updateClub_exception_wrong_auth() { @DisplayName("사용자는 동아리에 가입할 수 있다") void saveClubUser() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubUserSaveInput clubUserSaveInput = ClubUserSaveInput.builder().clubPassword("clubPassword").build(); //when @@ -190,7 +188,7 @@ void saveClubUser() { @DisplayName("사용자가 동아리에 가입할 때 틀린 비밀번호를 입력하면 예외가 발생한다") void saveClubUser_exception_wrong_password() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubUserSaveInput clubUserSaveInput = ClubUserSaveInput.builder().clubPassword("wrong!").build(); //when //then @@ -208,7 +206,7 @@ void saveClubUser_exception_wrong_password() { @DisplayName("운영진은 동아리에 관련된 사용자 정보를 변경할 수 있다") void updateClubUser() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubUserUpdateInput clubUserUpdateInput = ClubUserUpdateInput.builder() .userId(1L).clubRole(ClubRole.STAFF.getKoreanName()).build(); @@ -230,7 +228,7 @@ void updateClubUser() { @DisplayName("운영진이 아니면 동아리에 속한 사용자의 정보를 변경하려 할 때 예외가 발생한다") void updateClubUser_exception_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubUserUpdateInput clubUserUpdateInput = ClubUserUpdateInput.builder() .userId(1L).clubRole(ClubRole.STAFF.getKoreanName()).build(); @@ -252,7 +250,7 @@ void updateClubUser_exception_wrong_permission() { @DisplayName("운영진은 동아리 인증 비밀번호를 바꿀 수 있다") void clubPasswordUpdate() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubPswdUpdateInput clubPswdUpdateInput = ClubPswdUpdateInput.builder().id(1L).oldPassword("clubPassword").newPassword("newPassword").rePassword("newPassword").build(); User user = new User(); //when @@ -269,7 +267,7 @@ void clubPasswordUpdate() { @DisplayName("일반 멤버가 인증 비밀번호를 바꾸려고 하면 예외가 발생한다") void clubPasswordUpdate_exception_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubPswdUpdateInput clubPswdUpdateInput = ClubPswdUpdateInput.builder().oldPassword("clubPassword").newPassword("newPassword").rePassword("newPassword").build(); User user = new User(); //when @@ -289,7 +287,7 @@ void clubPasswordUpdate_exception_wrong_permission() { @DisplayName("운영진이 동아리 비밀번호를 잘못 입력하면 예외가 발생한다") void clubPasswordUpdate_exception_wrong_password() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubPswdUpdateInput clubPswdUpdateInput = ClubPswdUpdateInput.builder().oldPassword("wrong!").newPassword("newPassword").rePassword("newPassword").build(); User user = new User(); //when @@ -309,7 +307,7 @@ void clubPasswordUpdate_exception_wrong_password() { @DisplayName("운영진이 새로운 비밀번호와 확인 비밀번호를 다르게 입력하면 예외가 발생한다") void clubPasswordUpdate_exception_wrong_check_password() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubPswdUpdateInput clubPswdUpdateInput = ClubPswdUpdateInput.builder().oldPassword("clubPassword").newPassword("newPassword").rePassword("wrong!").build(); User user = new User(); //when diff --git a/src/test/java/com/example/moim/club/service/ClubQueryServiceImplTest.java b/src/test/java/com/example/moim/club/service/ClubQueryServiceImplTest.java index e4d01d7..24fe8c2 100644 --- a/src/test/java/com/example/moim/club/service/ClubQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/ClubQueryServiceImplTest.java @@ -2,7 +2,6 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.dto.request.ClubSearchCond; -import com.example.moim.club.dto.request.ClubUpdateInput; import com.example.moim.club.dto.response.ClubOutput; import com.example.moim.club.dto.response.ClubSearchOutput; import com.example.moim.club.entity.*; @@ -64,7 +63,7 @@ void init() { @DisplayName("동아리 정보로 동아리들을 조회할 수 있다") void searchClub() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ClubSearchCond clubSearchCond = ClubSearchCond.builder().search("searchs").build(); //when @@ -83,7 +82,7 @@ void searchClub() { @DisplayName("동아리에 속한 사용자는 동아리 정보를 조회할 수 있다") void findClub() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Long id = 1L; User user = new User(); //when @@ -104,7 +103,7 @@ void findClub() { @DisplayName("동아리에 속하지 않은 사용자는 제한된 동아리 정보를 조회할 수 있다") void findClub_not_member() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Long id = 1L; User user = new User(); //when diff --git a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java index 2fc715f..23e3101 100644 --- a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java @@ -2,7 +2,6 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.dto.request.NoticeInput; -import com.example.moim.club.dto.request.NoticeOutput; import com.example.moim.club.entity.*; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.NoticeRepository; @@ -17,7 +16,6 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; -import java.util.List; import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -64,7 +62,7 @@ void init() { @DisplayName("공지를 저장할 수 있다") void saveNotice() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); //when when(noticeRepository.save(any(Notice.class))).thenReturn(notice); diff --git a/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java b/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java index 68cce48..91726ef 100644 --- a/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java @@ -66,7 +66,7 @@ void init() { @DisplayName("동아리 별 공지들을 조회할 수 있다") void findNotice() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); notice.setCreatedDate(); notice.setUpdatedDate(); @@ -86,7 +86,7 @@ void findNotice() { @DisplayName("동아리에 등록된 공지가 없을 경우 빈 리스트를 반환한다") void findNotice_zero_notice() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); diff --git a/src/test/java/com/example/moim/match/MatchServiceTest.java b/src/test/java/com/example/moim/match/MatchServiceTest.java index c25c035..4b4085b 100644 --- a/src/test/java/com/example/moim/match/MatchServiceTest.java +++ b/src/test/java/com/example/moim/match/MatchServiceTest.java @@ -8,8 +8,6 @@ import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.dto.*; import com.example.moim.match.entity.*; -import com.example.moim.match.exception.MatchPermissionException; -import com.example.moim.match.exception.MatchRecordExpireException; import com.example.moim.match.exception.advice.MatchControllerAdvice; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.match.repository.MatchRepository; @@ -85,7 +83,7 @@ void setUp() { clubInput.setMainUniformColor("흰색"); clubInput.setSubUniformColor("검은색"); - club = Club.createClub(clubInput, null); + club = Club.from(clubInput, null); ReflectionTestUtils.setField(club, "id", 1L); user = new User(); diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java index f4572a2..ed625b1 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java @@ -87,7 +87,7 @@ void init() { @DisplayName("운영진은 일정을 생성할 수 있다") void saveSchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); User user = User.createUser(signupInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); @@ -110,7 +110,7 @@ void saveSchedule() { @DisplayName("일반 멤버는 일정을 생성할 때 예외가 발생한다") void saveSchedule_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); //when @@ -129,7 +129,7 @@ void saveSchedule_wrong_permission() { @DisplayName("운영진은 일정을 변경할 수 있다") void updateSchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); Schedule schedule = Schedule.from(club, scheduleInput); @@ -150,7 +150,7 @@ void updateSchedule() { @DisplayName("일반 멤버는 일정을 수정할 때 예외가 발생한다") void updateSchedule_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); //when @@ -169,7 +169,7 @@ void updateSchedule_wrong_permission() { @DisplayName("한달 일정 조회하기") void findSchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); @@ -191,7 +191,7 @@ void findSchedule() { @DisplayName("한달 일정이 없으면 빈 리스트를 반환한다") void findSchedule_zero_schedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); //when @@ -209,7 +209,7 @@ void findSchedule_zero_schedule() { @DisplayName("동아리의 하루 일정을 조회할 수 있다") void findDaySchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); @@ -231,7 +231,7 @@ void findDaySchedule() { @DisplayName("스케줄로 그 스케줄의 매치 신청 내역 등 자세한 정보를 볼 수 있다") void findScheduleDetail() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); Schedule schedule = Schedule.from(club, scheduleInput); schedule.setCreatedDate(); @@ -252,7 +252,7 @@ void findScheduleDetail() { @DisplayName("멤버는 일정 참가에 대해 재투표를 할 수 있다") void voteSchedule_re() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); User user = User.createUser(signupInput); @@ -271,7 +271,7 @@ void voteSchedule_re() { @DisplayName("멤버는 일정 참가에 대해 투표를 할 수 있다") void voteSchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); User user = User.createUser(signupInput); @@ -304,7 +304,7 @@ void deleteSchedule() { void voteEncourage() { //given Long id = 1L; - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); @@ -322,7 +322,7 @@ void voteEncourage() { @DisplayName("운영진은 일정 투표를 마감할 수 있다") void closeSchedule() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); @@ -339,7 +339,7 @@ void closeSchedule() { @DisplayName("일반 회원이 일정 투표를 마감할 때 예외가 발생한다") void closeSchedule_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createUserClub(user, club); From a9ddd5c03cf94adb92ad23fc6f5b036f0a9fcde9 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:31:14 +0900 Subject: [PATCH 04/33] =?UTF-8?q?refactor(#33):=20ScheduleService=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=ED=95=A8=EC=88=98=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/global/exception/ResponseCode.java | 3 + .../moim/main/service/MainService.java | 2 +- .../controller/ScheduleController.java | 2 +- .../repository/ScheduleRepositoryCustom.java | 2 +- .../repository/ScheduleRepositoryImpl.java | 2 +- .../schedule/service/ScheduleService.java | 59 ++++++++++++++----- .../ScheduleRepositoryImplTest.java | 3 +- .../ScheduleVoteRepositoryTest.java | 3 +- .../schedule/service/ScheduleServiceTest.java | 16 ++--- 9 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 86b9f9d..3651f74 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -60,6 +60,9 @@ public enum ResponseCode { CLUB_CHECK_PASSWORD_INCORRECT(HttpStatus.UNAUTHORIZED, "CLUB4005", "모임 확인 비밀번호가 틀렸습니다."), CLUB_USER_ALREADY_JOINED(HttpStatus.CONFLICT, "CLUB4006", "해당 사용자는 이미 모임에 가입되어 있습니다."), + // Schedule Error + SCHEDULE_NOT_FOUND(HttpStatus.BAD_REQUEST, "SCHEDULE4001", "존재하지 않는 일정입니다."), + // File Error FILE_MAX_SIZE_OVER(HttpStatus.PAYLOAD_TOO_LARGE, "FILE4001", "100MB 이하 파일만 업로드 할 수 있습니다."), FILE_CONTENT_TYPE_NOT_IMAGE(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "FILE4002", "이미지 파일만 업로드할 수 있습니다."), diff --git a/src/main/java/com/example/moim/main/service/MainService.java b/src/main/java/com/example/moim/main/service/MainService.java index e762186..3017e24 100644 --- a/src/main/java/com/example/moim/main/service/MainService.java +++ b/src/main/java/com/example/moim/main/service/MainService.java @@ -27,7 +27,7 @@ public NoClubMainOutput noClubMainPage (User user) { public MainOutput mainPage(Long clubId) { return new MainOutput(clubRepository.findById(clubId).get(), - scheduleService.findSchedule(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), + scheduleService.findMonthSchedule(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), clubId, null, null))); } } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 2a308c5..00fbdc0 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -28,7 +28,7 @@ public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUp @GetMapping(value = "/schedule", produces = MediaType.APPLICATION_JSON_VALUE) public List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleService.findSchedule(scheduleSearchInput); + return scheduleService.findMonthSchedule(scheduleSearchInput); } @GetMapping(value = "/schedule/day", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java index d472472..fc92d38 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java @@ -9,5 +9,5 @@ public interface ScheduleRepositoryCustom { List findByClubAndTime(Club club, LocalDateTime startTime, LocalDateTime endTime, String search, String category); - Schedule findScheduleById(Long id); + Schedule findWithClubById(Long id); } diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java index 11192de..e992458 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -55,7 +55,7 @@ private BooleanExpression categoryContains(String category) { } @Override - public Schedule findScheduleById(Long id) { + public Schedule findWithClubById(Long id) { return queryFactory .selectFrom(schedule) .join(schedule.club, club).fetchJoin() diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java index 4331a4e..453d7cd 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleService.java @@ -42,8 +42,8 @@ public class ScheduleService { private final MatchApplicationRepository matchApplicationRepository; public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { - Club club = clubRepository.findById(scheduleInput.getClubId()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); - UserClub userClub = userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + Club club = getClub(scheduleInput.getClubId()); + UserClub userClub = getUserClub(club, user); if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } @@ -60,13 +60,22 @@ public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { @Transactional public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long scheduleId, User user) { - UserClub userClub = userClubRepository.findByClubAndUser(clubRepository.findById(scheduleUpdateInput.getClubId()).get(), user).get(); + Club club = getClub(scheduleUpdateInput.getClubId()); + UserClub userClub = getUserClub(club, user); + if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } - Schedule schedule = scheduleRepository.findById(scheduleId).get(); + Schedule schedule = getSchedule(scheduleId); + schedule.update(scheduleUpdateInput); + + /** + * TODO: 알림 리팩터링 버전으로 다시 적용해야 함 + */ + eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); + return new ScheduleOutput(schedule); } @@ -75,8 +84,10 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo * @param scheduleSearchInput * @return */ - public List findSchedule(ScheduleSearchInput scheduleSearchInput) { - return scheduleRepository.findByClubAndTime(clubRepository.findById(scheduleSearchInput.getClubId()).get(), + public List findMonthSchedule(ScheduleSearchInput scheduleSearchInput) { + Club club = getClub(scheduleSearchInput.getClubId()); + + return scheduleRepository.findByClubAndTime(club, LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, 1, 0, 0, 0).minusDays(6), LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, Month.of(scheduleSearchInput.getDate() % 100).minLength(), 23, 59, 59).plusDays(6), scheduleSearchInput.getSearch(), @@ -87,13 +98,16 @@ public List findSchedule(ScheduleSearchInput scheduleSearchInput public List findDaySchedule(ScheduleSearchInput scheduleSearchInput) { LocalDateTime searchDate = LocalDateTime.of(scheduleSearchInput.getDate() / 10000, (scheduleSearchInput.getDate() / 100) % 100, scheduleSearchInput.getDate() % 100, 0, 0, 0); - return scheduleRepository.findByClubAndTime(clubRepository.findById(scheduleSearchInput.getClubId()).get(), + Club club = getClub(scheduleSearchInput.getClubId()); + + return scheduleRepository.findByClubAndTime(club, searchDate, searchDate.plusDays(1), scheduleSearchInput.getSearch(), scheduleSearchInput.getCategory()) .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } public ScheduleDetailOutput findScheduleDetail(Long id) { - Schedule schedule = scheduleRepository.findById(id).get(); + Schedule schedule = getSchedule(id); + return new ScheduleDetailOutput(schedule, // scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); @@ -106,16 +120,20 @@ public ScheduleDetailOutput findScheduleDetail(Long id) { */ @Transactional public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { - Schedule schedule = scheduleRepository.findScheduleById(scheduleVoteInput.getId()); + Schedule schedule = getSchedule(scheduleVoteInput.getId()); Optional originalScheduleVote = scheduleVoteRepository.findByScheduleAndUser(schedule, user); + //투표 처음이면 if (originalScheduleVote.isEmpty()) { scheduleVoteRepository.save(ScheduleVote.createScheduleVote(user, schedule, scheduleVoteInput.getAttendance())); schedule.vote(scheduleVoteInput.getAttendance()); - } else {// 재투표인 경우 + } else { // 재투표인 경우 schedule.reVote(originalScheduleVote.get().getAttendance(), scheduleVoteInput.getAttendance()); originalScheduleVote.get().changeAttendance(scheduleVoteInput.getAttendance()); } + /** + * TODO: 왜 알림 보내려고 했는지 확인하고 로직 추가하기 + */ // if (scheduleVoteInput.getAttendance().equals("attend")) { // eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); // } @@ -136,7 +154,7 @@ public void deleteSchedule(Long id) { * @param id */ public void voteEncourage(Long id) { - Schedule schedule = scheduleRepository.findScheduleById(id); + Schedule schedule = scheduleRepository.findWithClubById(id); List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); } @@ -147,8 +165,8 @@ public void voteEncourage(Long id) { */ @Transactional public void closeSchedule(Long id, User user) { - Schedule schedule = scheduleRepository.findById(id).get(); - UserClub userClub = userClubRepository.findByClubAndUser(schedule.getClub(), user).get(); + Schedule schedule = scheduleRepository.findWithClubById(id); + UserClub userClub = getUserClub(schedule.getClub(), user); if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } @@ -157,6 +175,19 @@ public void closeSchedule(Long id, User user) { } public void saveComment(CommentInput commentInput, User user) { - commentRepository.save(Comment.createComment(user, scheduleRepository.findById(commentInput.getId()).get(), commentInput.getContents())); + Schedule schedule = getSchedule(commentInput.getId()); + commentRepository.save(Comment.createComment(user, schedule, commentInput.getContents())); + } + + private Club getClub(Long clubId) { + return clubRepository.findById(clubId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); + } + + private UserClub getUserClub(Club club, User user) { + return userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + } + + private Schedule getSchedule(Long scheduleId) { + return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); } } diff --git a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java index 3d5294e..5b3172e 100644 --- a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java +++ b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java @@ -14,7 +14,6 @@ import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @SqlGroup({ @@ -50,7 +49,7 @@ void findScheduleById() { //given Long scheduleId = 1L; //when - Schedule result = scheduleRepository.findScheduleById(scheduleId); + Schedule result = scheduleRepository.findWithClubById(scheduleId); //then assertThat(result.getTitle()).isEqualTo("운동 매치 스케줄"); assertThat(result.getComment().get(0).getContents()).isEqualTo("회사 면접이 잡혀있어서 못 갑니다"); diff --git a/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java index 6b28ddd..b9aca18 100644 --- a/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java +++ b/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java @@ -12,7 +12,6 @@ import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @SqlGroup({ @@ -30,7 +29,7 @@ class ScheduleVoteRepositoryTest { @DisplayName("스케줄 객체로 투표 현황을 조회할 수 있다") void findBySchedule() { //given - Schedule schedule = scheduleRepository.findScheduleById(1L); + Schedule schedule = scheduleRepository.findWithClubById(1L); //when List result = scheduleVoteRepository.findBySchedule(schedule); //then diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java index ed625b1..7786277 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java @@ -176,7 +176,7 @@ void findSchedule() { //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleService.findSchedule(scheduleSearchInput); + List result = scheduleService.findMonthSchedule(scheduleSearchInput); //then assertThat(result.size()).isEqualTo(1); @@ -197,7 +197,7 @@ void findSchedule_zero_schedule() { //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of()); - List result = scheduleService.findSchedule(scheduleSearchInput); + List result = scheduleService.findMonthSchedule(scheduleSearchInput); //then assertThat(result.size()).isEqualTo(0); @@ -258,12 +258,12 @@ void voteSchedule_re() { User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "true"); //when - when(scheduleRepository.findScheduleById(any(Long.class))).thenReturn(schedule); + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); scheduleService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findScheduleById(any(Long.class)); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); } @@ -277,13 +277,13 @@ void voteSchedule() { User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "false"); //when - when(scheduleRepository.findScheduleById(any(Long.class))).thenReturn(schedule); + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); scheduleService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findScheduleById(any(Long.class)); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); verify(scheduleVoteRepository, times(1)).save(any(ScheduleVote.class)); } @@ -309,11 +309,11 @@ void voteEncourage() { Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); //when - when(scheduleRepository.findScheduleById(any(Long.class))).thenReturn(schedule); + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); scheduleService.voteEncourage(id); //then - verify(scheduleRepository, times(1)).findScheduleById(any(Long.class)); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(userClubRepository, times(1)).findUserByClub(any(Club.class)); verify(applicationEventPublisher, times(1)).publishEvent(any(ScheduleEncourageEvent.class)); } From 359accca23fd5e8f9d241c1517150bfa98a8cb45 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:38:24 +0900 Subject: [PATCH 05/33] =?UTF-8?q?refactor(#33):=20URL=20=EB=AA=A8=EB=91=90?= =?UTF-8?q?=20=EB=B3=B5=EC=88=98=ED=98=95=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD.=20RESTful=20=EB=AA=85=EB=AA=85=20=EA=B7=9C=EC=B9=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/club/controller/ClubController.java | 12 +++++----- .../club/controller/NoticeController.java | 4 ++-- .../controller/ScheduleController.java | 24 +++++++++---------- .../schedule/service/ScheduleService.java | 8 +++---- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/example/moim/club/controller/ClubController.java b/src/main/java/com/example/moim/club/controller/ClubController.java index bb1b11c..6140dbf 100644 --- a/src/main/java/com/example/moim/club/controller/ClubController.java +++ b/src/main/java/com/example/moim/club/controller/ClubController.java @@ -28,14 +28,14 @@ public class ClubController implements ClubControllerDocs{ private final UserRepository userRepository; - @PostMapping(value = "/club", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PostMapping(value = "/clubs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public BaseResponse clubSave(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @ModelAttribute @Valid ClubInput clubInput) throws IOException { // public BaseResponse clubSave(@ModelAttribute @Valid ClubInput clubInput) throws IOException { // User user = userRepository.findById(1L).get(); return BaseResponse.onSuccess(clubCommandService.saveClub(userDetailsImpl.getUser(), clubInput), ResponseCode.OK); } - @PatchMapping(value = "/club/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(value = "/clubs/{id}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public BaseResponse clubUpdate(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @ModelAttribute ClubUpdateInput clubUpdateInput, @PathVariable("id") Long clubId) throws IOException { // public BaseResponse clubUpdate(@ModelAttribute ClubUpdateInput clubUpdateInput, @PathVariable("id") Long clubId) throws IOException { // User user = userRepository.findById(1L).get(); @@ -48,7 +48,7 @@ public BaseResponse> findClubs(@AuthenticationPrincipal U return BaseResponse.onSuccess(clubQueryService.searchClub(clubSearchCond), ResponseCode.OK); } - @PostMapping("/club/{id}/join") + @PostMapping("/clubs/{id}/join") public BaseResponse clubUserSave(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @RequestBody ClubUserSaveInput clubUserSaveInput, @PathVariable("id") Long clubId) { // public BaseResponse clubUserSave(@RequestBody ClubUserSaveInput clubUserSaveInput, @PathVariable("id") Long clubId) { // User user = userRepository.findById(1L).get(); @@ -60,21 +60,21 @@ public BaseResponse clubUserSave(@AuthenticationPrincipal UserDe // return clubService.inviteClubUser(userDetailsImpl.getUser(), clubInviteInput); // } - @PatchMapping("/club/{id}/role") + @PatchMapping("/clubs/{id}/role") public BaseResponse clubUserUpdate(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @RequestBody ClubUserUpdateInput clubInput, @PathVariable("id") Long clubId) { // public BaseResponse clubUserUpdate(@RequestBody ClubUserUpdateInput clubInput, @PathVariable("id") Long clubId) { // User user = userRepository.findById(1L).get(); return BaseResponse.onSuccess(clubCommandService.updateClubUser(userDetailsImpl.getUser(), clubInput, clubId), ResponseCode.OK); } - @GetMapping("/club/{id}") + @GetMapping("/clubs/{id}") public BaseResponse clubFind(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable("id") Long clubId) { // public BaseResponse clubFind(@Parameter(description = "조회할 모임 ID", required = true, example = "1") @PathVariable("id") Long id) { // User user = userRepository.findById(1L).get(); return BaseResponse.onSuccess(clubQueryService.findClub(clubId, userDetailsImpl.getUser()), ResponseCode.OK); } - @PatchMapping(value = "/club/{id}/password", consumes = MediaType.APPLICATION_JSON_VALUE) + @PatchMapping(value = "/clubs/{id}/password", consumes = MediaType.APPLICATION_JSON_VALUE) public BaseResponse clubPasswordUpdate(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @RequestBody @Valid ClubPswdUpdateInput clubPswdUpdateInput, @PathVariable("id") Long clubId) { // public BaseResponse clubPasswordUpdate(@RequestBody @Valid ClubPswdUpdateInput clubPswdUpdateInput, @PathVariable("id") Long clubId) { // User user = userRepository.findById(1L).get(); diff --git a/src/main/java/com/example/moim/club/controller/NoticeController.java b/src/main/java/com/example/moim/club/controller/NoticeController.java index 23587ab..0191738 100644 --- a/src/main/java/com/example/moim/club/controller/NoticeController.java +++ b/src/main/java/com/example/moim/club/controller/NoticeController.java @@ -26,7 +26,7 @@ public class NoticeController implements NoticeControllerDocs { /** * FIXME: 응답 값이 무조건 있어야 함. user 의 권한 체크는 안해도 되나? */ - @PostMapping("/notice") + @PostMapping("/notices") public BaseResponse noticeSave(NoticeInput noticeInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { noticeCommandService.saveNotice(noticeInput); return BaseResponse.onSuccess(null, ResponseCode.OK); @@ -36,7 +36,7 @@ public BaseResponse noticeSave(NoticeInput noticeInput, @AuthenticationPrincipal * FIXME: 공지를 시간 순으로 정렬하지 않아도 되나? * @return */ - @GetMapping("/notice/{clubId}") + @GetMapping("/notices/{clubId}") public BaseResponse> noticeSave(@PathVariable Long clubId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { return BaseResponse.onSuccess(noticeQueryService.findNotice(clubId), ResponseCode.OK); } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 00fbdc0..23c4b9c 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -16,52 +16,52 @@ public class ScheduleController implements ScheduleControllerDocs{ private final ScheduleService scheduleService; - @PostMapping(value = "/schedule") + @PostMapping(value = "/schedules") public ScheduleOutput scheduleSave(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { return scheduleService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); } - @PatchMapping("/schedule/{scheduleId}") - public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("scheduleId") Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.updateSchedule(scheduleUpdateInput, scheduleId, userDetailsImpl.getUser()); + @PatchMapping("/schedules/{id}") + public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + return scheduleService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); } - @GetMapping(value = "/schedule", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) public List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { return scheduleService.findMonthSchedule(scheduleSearchInput); } - @GetMapping(value = "/schedule/day", produces = MediaType.APPLICATION_JSON_VALUE) + @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) public List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { return scheduleService.findDaySchedule(scheduleSearchInput); } - @GetMapping("/schedule/{id}") + @GetMapping("/schedules/{id}") public ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id) { return scheduleService.findScheduleDetail(id); } - @DeleteMapping("/schedule/{id}") + @DeleteMapping("/schedules/{id}") public void scheduleDelete(@PathVariable Long id) { scheduleService.deleteSchedule(id); } - @PatchMapping("/schedule/vote") + @PatchMapping("/schedules/vote") public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { scheduleService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); } - @PostMapping("/schedule/encourage/{id}") + @PostMapping("/schedules/encourage/{id}") public void voteEncourage(@PathVariable Long id) { scheduleService.voteEncourage(id); } - @PatchMapping("/schedule/close/{id}") + @PatchMapping("/schedules/close/{id}") public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { scheduleService.closeSchedule(id, userDetailsImpl.getUser()); } - @PostMapping("/schedule/comment") + @PostMapping("/schedules/{id}/comments") public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { scheduleService.saveComment(commentInput, userDetailsImpl.getUser()); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java index 453d7cd..1d197b5 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleService.java @@ -59,7 +59,7 @@ public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { } @Transactional - public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long scheduleId, User user) { + public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user) { Club club = getClub(scheduleUpdateInput.getClubId()); UserClub userClub = getUserClub(club, user); @@ -67,7 +67,7 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); } - Schedule schedule = getSchedule(scheduleId); + Schedule schedule = getSchedule(id); schedule.update(scheduleUpdateInput); @@ -105,8 +105,8 @@ public List findDaySchedule(ScheduleSearchInput scheduleSearchIn .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } - public ScheduleDetailOutput findScheduleDetail(Long id) { - Schedule schedule = getSchedule(id); + public ScheduleDetailOutput findScheduleDetail(Long scheduleId) { + Schedule schedule = getSchedule(scheduleId); return new ScheduleDetailOutput(schedule, // scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), From 0e6c904bcffd08593abd630e6106b27347180486 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:50:55 +0900 Subject: [PATCH 06/33] =?UTF-8?q?refactor(#33):=20ScheduleCategory=20Enum?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/global/exception/ResponseCode.java | 3 ++- .../moim/schedule/entity/Schedule.java | 8 +++--- .../schedule/entity/ScheduleCategory.java | 27 +++++++++++++++++++ .../schedule/service/ScheduleService.java | 5 ---- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 3651f74..f700d29 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -76,7 +76,8 @@ public enum ResponseCode { INVALID_ACTIVITY_AREA(HttpStatus.BAD_REQUEST, "ENUM4005", "유효하지 않은 지역입니다."), INVALID_MAIN_FOOT(HttpStatus.BAD_REQUEST, "ENUM4006", "유효하지 않은 주발입니다."), INVALID_SPORTS_TYPE(HttpStatus.BAD_REQUEST, "ENUM4007", "유효하지 않은 종목입니다."), - INVALID_CLUB_ROLE(HttpStatus.BAD_REQUEST, "ENUM4008", "유효하지 않은 모임 역할입니다."); + INVALID_CLUB_ROLE(HttpStatus.BAD_REQUEST, "ENUM4008", "유효하지 않은 모임 역할입니다."), + INVALID_SCHEDULE_CATEGORY(HttpStatus.BAD_REQUEST, "ENUM4009", "유효하지 않은 일정 종류입니다."); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 480be61..6ceb770 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -1,9 +1,11 @@ package com.example.moim.schedule.entity; import com.example.moim.club.entity.Club; +import com.example.moim.global.exception.ResponseCode; import com.example.moim.schedule.dto.ScheduleInput; import com.example.moim.schedule.dto.ScheduleUpdateInput; import com.example.moim.global.entity.BaseEntity; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -26,7 +28,7 @@ public class Schedule extends BaseEntity { private LocalDateTime startTime; private LocalDateTime endTime; private int minPeople; - private String category; + private ScheduleCategory category; private String note; private int attend; private int nonAttend; @@ -43,7 +45,7 @@ public static Schedule from(Club club, ScheduleInput scheduleInput) { schedule.startTime = scheduleInput.getStartTime(); schedule.endTime = scheduleInput.getEndTime(); schedule.minPeople = scheduleInput.getMinPeople(); - schedule.category = scheduleInput.getCategory(); + schedule.category = ScheduleCategory.fromKoreanName(scheduleInput.getCategory()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_SCHEDULE_CATEGORY)); if (scheduleInput.getNote() != null) { schedule.note = scheduleInput.getNote(); } @@ -91,7 +93,7 @@ public void update(ScheduleUpdateInput scheduleUpdateInput) { this.minPeople = scheduleUpdateInput.getMinPeople(); } if (scheduleUpdateInput.getCategory() != null) { - this.category = scheduleUpdateInput.getCategory(); + this.category = ScheduleCategory.fromKoreanName(scheduleUpdateInput.getCategory()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_SCHEDULE_CATEGORY)); } if (scheduleUpdateInput.getNote() != null) { this.note = scheduleUpdateInput.getNote(); diff --git a/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java new file mode 100644 index 0000000..2751770 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java @@ -0,0 +1,27 @@ +package com.example.moim.schedule.entity; + +import java.util.Arrays; +import java.util.Optional; + +public enum ScheduleCategory { + REGULAR_TRAINING("정기 운동"), + TOURNAMENT("대회"), + FRIENDLY_MATCH("친선 매치"), + ETC("기타"); + + private final String koreanName; + + ScheduleCategory(String koreanName) { + this.koreanName = koreanName; + } + + public String getKoreanName() { + return koreanName; + } + + public static Optional fromKoreanName(String koreanName) { + return Arrays.stream(values()) + .filter(g -> g.getKoreanName().equals(koreanName)) + .findFirst(); + } +} diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java index 1d197b5..6d7ce57 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleService.java @@ -79,11 +79,6 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo return new ScheduleOutput(schedule); } - /** - * TODO: 이름 명확하게 변경하기. findMontSchedule 등으로 - * @param scheduleSearchInput - * @return - */ public List findMonthSchedule(ScheduleSearchInput scheduleSearchInput) { Club club = getClub(scheduleSearchInput.getClubId()); From 095b3adff9482239f2d652a21edd949291ba0e5f Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 15:53:49 +0900 Subject: [PATCH 07/33] =?UTF-8?q?refactor(#33):=20ScheduleInput=20Valid=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/moim/schedule/dto/ScheduleInput.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java index 3cd2feb..5495795 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java @@ -13,6 +13,7 @@ @Data public class ScheduleInput { + @NotBlank(message = "클럽 아이디를 입력해주세요.") private Long clubId; @NotBlank(message = "일정 제목을 입력해주세요.") private String title; @@ -20,20 +21,20 @@ public class ScheduleInput { private String location; @Schema(pattern = "yyyy-MM-dd HH:mm", description = "yyyy-MM-dd HH:mm") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotNull(message = "일정 시작 시간을 입력해주세요.") + @NotBlank(message = "일정 시작 시간을 입력해주세요.") private LocalDateTime startTime; @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotNull(message = "일정 종료 시간을 입력해주세요.") + @NotBlank(message = "일정 종료 시간을 입력해주세요.") private LocalDateTime endTime; @Min(value = 1, message = "참여 인원은 1명 이상이어야 합니다") - @NotNull(message = "최소 참여 인원을 입력해주세요.") + @NotBlank(message = "최소 참여 인원을 입력해주세요.") private int minPeople;//참여인원수 @NotBlank(message = "일정 카테고리를 입력해 주세요.") private String category; private String note; @Builder - public ScheduleInput(Long clubId, String title, String location, @NotNull(message = "일정 시작 시간을 입력해주세요.") LocalDateTime startTime, @NotNull(message = "일정 종료 시간을 입력해주세요.") LocalDateTime endTime, @NotNull(message = "최소 참여 인원을 입력해주세요.") int minPeople, String category, String note) { + public ScheduleInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, int minPeople, String category, String note) { this.clubId = clubId; this.title = title; this.location = location; From 3ef21c7bb52d760e36a16b5ff98e0c443bab94c9 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 16:34:49 +0900 Subject: [PATCH 08/33] =?UTF-8?q?refactor(#33):=20schedule=20=EC=97=90=20C?= =?UTF-8?q?QS=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/main/service/MainService.java | 6 +- .../moim/match/service/MatchService.java | 4 +- .../controller/ScheduleCommentController.java | 4 + .../controller/ScheduleController.java | 26 ++-- .../schedule/dto/ScheduleDetailOutput.java | 2 +- .../moim/schedule/dto/ScheduleOutput.java | 2 +- .../moim/schedule/entity/Schedule.java | 1 + .../repository/ScheduleRepositoryImpl.java | 5 +- .../service/ScheduleCommandService.java | 15 ++ ...e.java => ScheduleCommandServiceImpl.java} | 35 +---- .../service/ScheduleQueryService.java | 13 ++ .../service/ScheduleQueryServiceImpl.java | 66 ++++++++ .../controller/ScheduleVoteController.java | 4 + ...va => ScheduleCommandServiceImplTest.java} | 132 +++------------- .../service/ScheduleQueryServiceImplTest.java | 144 ++++++++++++++++++ .../sql/comment-repository-test-data.sql | 4 +- .../schedule-vote-repository-test-data.sql | 4 +- 17 files changed, 300 insertions(+), 167 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java create mode 100644 src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java rename src/main/java/com/example/moim/schedule/service/{ScheduleService.java => ScheduleCommandServiceImpl.java} (76%) create mode 100644 src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java create mode 100644 src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java create mode 100644 src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java rename src/test/java/com/example/moim/schedule/service/{ScheduleServiceTest.java => ScheduleCommandServiceImplTest.java} (69%) create mode 100644 src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java diff --git a/src/main/java/com/example/moim/main/service/MainService.java b/src/main/java/com/example/moim/main/service/MainService.java index 3017e24..d4bb698 100644 --- a/src/main/java/com/example/moim/main/service/MainService.java +++ b/src/main/java/com/example/moim/main/service/MainService.java @@ -2,10 +2,10 @@ import com.example.moim.schedule.dto.ScheduleSearchInput; import com.example.moim.club.repository.ClubRepository; -import com.example.moim.schedule.service.ScheduleService; import com.example.moim.main.dto.MainOutput; import com.example.moim.main.dto.NoClubMainOutput; import com.example.moim.main.dto.RecommendClubListOutput; +import com.example.moim.schedule.service.ScheduleQueryService; import com.example.moim.user.entity.User; import com.example.moim.user.repository.UserRepository; import lombok.RequiredArgsConstructor; @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor public class MainService { - private final ScheduleService scheduleService; + private final ScheduleQueryService scheduleQueryService; private final UserRepository userRepository; private final ClubRepository clubRepository; @@ -27,7 +27,7 @@ public NoClubMainOutput noClubMainPage (User user) { public MainOutput mainPage(Long clubId) { return new MainOutput(clubRepository.findById(clubId).get(), - scheduleService.findMonthSchedule(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), + scheduleQueryService.findMonthSchedule(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), clubId, null, null))); } } diff --git a/src/main/java/com/example/moim/match/service/MatchService.java b/src/main/java/com/example/moim/match/service/MatchService.java index 76a1cae..442bca5 100644 --- a/src/main/java/com/example/moim/match/service/MatchService.java +++ b/src/main/java/com/example/moim/match/service/MatchService.java @@ -13,7 +13,7 @@ import com.example.moim.schedule.repository.ScheduleRepository; import com.example.moim.schedule.repository.ScheduleVoteRepository; import com.example.moim.club.repository.UserClubRepository; -import com.example.moim.schedule.service.ScheduleService; +import com.example.moim.schedule.service.ScheduleCommandServiceImpl; import com.example.moim.match.dto.*; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.match.repository.MatchRepository; @@ -45,7 +45,7 @@ public class MatchService { private final MatchRepository matchRepository; private final UserClubRepository userClubRepository; private final ClubRepository clubRepository; - private final ScheduleService scheduleService; + private final ScheduleCommandServiceImpl scheduleCommandServiceImpl; private final ScheduleRepository scheduleRepository; private final MatchApplicationRepository matchApplicationRepository; private final ApplicationEventPublisher eventPublisher; diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java new file mode 100644 index 0000000..50304eb --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java @@ -0,0 +1,4 @@ +package com.example.moim.schedule.comment.controller; + +public class ScheduleCommentController { +} diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 23c4b9c..5399ef7 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -1,7 +1,8 @@ package com.example.moim.schedule.controller; import com.example.moim.schedule.dto.*; -import com.example.moim.schedule.service.ScheduleService; +import com.example.moim.schedule.service.ScheduleCommandService; +import com.example.moim.schedule.service.ScheduleQueryService; import com.example.moim.user.dto.UserDetailsImpl; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -14,55 +15,56 @@ @RestController @RequiredArgsConstructor public class ScheduleController implements ScheduleControllerDocs{ - private final ScheduleService scheduleService; + private final ScheduleCommandService scheduleCommandService; + private final ScheduleQueryService scheduleQueryService; @PostMapping(value = "/schedules") public ScheduleOutput scheduleSave(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); + return scheduleCommandService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); } @PatchMapping("/schedules/{id}") public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); + return scheduleCommandService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); } @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) public List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleService.findMonthSchedule(scheduleSearchInput); + return scheduleQueryService.findMonthSchedule(scheduleSearchInput); } @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) public List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleService.findDaySchedule(scheduleSearchInput); + return scheduleQueryService.findDaySchedule(scheduleSearchInput); } @GetMapping("/schedules/{id}") public ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id) { - return scheduleService.findScheduleDetail(id); + return scheduleQueryService.findScheduleDetail(id); } @DeleteMapping("/schedules/{id}") public void scheduleDelete(@PathVariable Long id) { - scheduleService.deleteSchedule(id); + scheduleCommandService.deleteSchedule(id); } @PatchMapping("/schedules/vote") public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + scheduleCommandService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); } @PostMapping("/schedules/encourage/{id}") public void voteEncourage(@PathVariable Long id) { - scheduleService.voteEncourage(id); + scheduleCommandService.voteEncourage(id); } @PatchMapping("/schedules/close/{id}") public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleService.closeSchedule(id, userDetailsImpl.getUser()); + scheduleCommandService.closeSchedule(id, userDetailsImpl.getUser()); } @PostMapping("/schedules/{id}/comments") public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleService.saveComment(commentInput, userDetailsImpl.getUser()); + scheduleCommandService.saveComment(commentInput, userDetailsImpl.getUser()); } } diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index 29d0076..378b42c 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -37,7 +37,7 @@ public ScheduleDetailOutput(Schedule schedule, List MatchA this.period = schedule.getStartTime().toLocalDate().toString() + " " + schedule.getStartTime().toLocalTime().toString() + " ~ " + schedule.getEndTime().toLocalTime().toString(); this.minPeople = schedule.getMinPeople(); - this.category = schedule.getCategory(); + this.category = schedule.getCategory().getKoreanName(); this.note = schedule.getNote(); this.attend = schedule.getAttend(); this.nonAttend = schedule.getNonAttend(); diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleOutput.java index bb22715..cb9bba0 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleOutput.java @@ -23,7 +23,7 @@ public ScheduleOutput(Schedule schedule) { this.date = schedule.getStartTime().toLocalDate(); this.period = schedule.getStartTime().toLocalTime().toString() + " ~ " + schedule.getEndTime().toLocalTime().toString(); this.minPeople = schedule.getMinPeople(); - this.category = schedule.getCategory(); + this.category = schedule.getCategory().getKoreanName(); if (schedule.getNote() != null) { this.note = schedule.getNote(); } diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 6ceb770..724a70e 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -28,6 +28,7 @@ public class Schedule extends BaseEntity { private LocalDateTime startTime; private LocalDateTime endTime; private int minPeople; + @Enumerated(value = EnumType.STRING) private ScheduleCategory category; private String note; private int attend; diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java index e992458..85885dc 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -2,6 +2,7 @@ import com.example.moim.club.entity.Club; import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.entity.ScheduleCategory; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import jakarta.persistence.EntityManager; @@ -47,9 +48,9 @@ private BooleanExpression searchContains(String search) { return null; } - private BooleanExpression categoryContains(String category) { + private BooleanExpression categoryEq(String category) { if (hasText(category)) { - return schedule.category.contains(category); + return schedule.category.eq(ScheduleCategory.valueOf(category)); } return null; } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java new file mode 100644 index 0000000..bfa1eb0 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java @@ -0,0 +1,15 @@ +package com.example.moim.schedule.service; + +import com.example.moim.schedule.dto.*; +import com.example.moim.user.entity.User; + +public interface ScheduleCommandService { + ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user); + ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user); + void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); + void deleteSchedule(Long id); + void voteEncourage(Long id); + void closeSchedule(Long id, User user); + void saveComment(CommentInput commentInput, User user); + +} diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java similarity index 76% rename from src/main/java/com/example/moim/schedule/service/ScheduleService.java rename to src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java index 6d7ce57..19fbce8 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -4,7 +4,6 @@ import com.example.moim.club.repository.*; import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.match.dto.MatchApplyClubOutput; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; @@ -23,16 +22,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.time.Month; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor -public class ScheduleService { +public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final ClubRepository clubRepository; private final ScheduleRepository scheduleRepository; private final UserClubRepository userClubRepository; @@ -79,35 +75,6 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo return new ScheduleOutput(schedule); } - public List findMonthSchedule(ScheduleSearchInput scheduleSearchInput) { - Club club = getClub(scheduleSearchInput.getClubId()); - - return scheduleRepository.findByClubAndTime(club, - LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, 1, 0, 0, 0).minusDays(6), - LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, Month.of(scheduleSearchInput.getDate() % 100).minLength(), 23, 59, 59).plusDays(6), - scheduleSearchInput.getSearch(), - scheduleSearchInput.getCategory()) - .stream().map(ScheduleOutput::new).collect(Collectors.toList()); - } - - public List findDaySchedule(ScheduleSearchInput scheduleSearchInput) { - LocalDateTime searchDate = LocalDateTime.of(scheduleSearchInput.getDate() / 10000, (scheduleSearchInput.getDate() / 100) % 100, scheduleSearchInput.getDate() % 100, - 0, 0, 0); - Club club = getClub(scheduleSearchInput.getClubId()); - - return scheduleRepository.findByClubAndTime(club, - searchDate, searchDate.plusDays(1), scheduleSearchInput.getSearch(), scheduleSearchInput.getCategory()) - .stream().map(ScheduleOutput::new).collect(Collectors.toList()); - } - - public ScheduleDetailOutput findScheduleDetail(Long scheduleId) { - Schedule schedule = getSchedule(scheduleId); - - return new ScheduleDetailOutput(schedule, -// scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), - matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); - } - /** * TODO: void -> 기본 응답 * @param scheduleVoteInput diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java new file mode 100644 index 0000000..1d8fc6e --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java @@ -0,0 +1,13 @@ +package com.example.moim.schedule.service; + +import com.example.moim.schedule.dto.ScheduleDetailOutput; +import com.example.moim.schedule.dto.ScheduleOutput; +import com.example.moim.schedule.dto.ScheduleSearchInput; + +import java.util.List; + +public interface ScheduleQueryService { + List findMonthSchedule(ScheduleSearchInput scheduleSearchInput); + List findDaySchedule(ScheduleSearchInput scheduleSearchInput); + ScheduleDetailOutput findScheduleDetail(Long scheduleId); +} diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java new file mode 100644 index 0000000..f6cbcdd --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -0,0 +1,66 @@ +package com.example.moim.schedule.service; + +import com.example.moim.club.entity.Club; +import com.example.moim.club.repository.ClubRepository; +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.match.dto.MatchApplyClubOutput; +import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.schedule.dto.ScheduleDetailOutput; +import com.example.moim.schedule.dto.ScheduleOutput; +import com.example.moim.schedule.dto.ScheduleSearchInput; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; +import com.example.moim.schedule.repository.ScheduleRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.Month; +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ScheduleQueryServiceImpl implements ScheduleQueryService { + + private final ScheduleRepository scheduleRepository; + private final ClubRepository clubRepository; + private final MatchApplicationRepository matchApplicationRepository; + + public List findMonthSchedule(ScheduleSearchInput scheduleSearchInput) { + Club club = getClub(scheduleSearchInput.getClubId()); + + return scheduleRepository.findByClubAndTime(club, + LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, 1, 0, 0, 0).minusDays(6), + LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, Month.of(scheduleSearchInput.getDate() % 100).minLength(), 23, 59, 59).plusDays(6), + scheduleSearchInput.getSearch(), + scheduleSearchInput.getCategory()) + .stream().map(ScheduleOutput::new).collect(Collectors.toList()); + } + + public List findDaySchedule(ScheduleSearchInput scheduleSearchInput) { + LocalDateTime searchDate = LocalDateTime.of(scheduleSearchInput.getDate() / 10000, (scheduleSearchInput.getDate() / 100) % 100, scheduleSearchInput.getDate() % 100, + 0, 0, 0); + Club club = getClub(scheduleSearchInput.getClubId()); + + return scheduleRepository.findByClubAndTime(club, + searchDate, searchDate.plusDays(1), scheduleSearchInput.getSearch(), scheduleSearchInput.getCategory()) + .stream().map(ScheduleOutput::new).collect(Collectors.toList()); + } + + public ScheduleDetailOutput findScheduleDetail(Long scheduleId) { + Schedule schedule = getSchedule(scheduleId); + + return new ScheduleDetailOutput(schedule, +// scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), + matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); + } + + private Club getClub(Long clubId) { + return clubRepository.findById(clubId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); + } + + private Schedule getSchedule(Long scheduleId) { + return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); + } +} diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java new file mode 100644 index 0000000..6b65cf1 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -0,0 +1,4 @@ +package com.example.moim.schedule.vote.controller; + +public class ScheduleVoteController { +} diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java similarity index 69% rename from src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java rename to src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java index 7786277..d83508a 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java @@ -6,8 +6,6 @@ import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.match.entity.Match; -import com.example.moim.match.entity.MatchApplication; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; @@ -39,7 +37,7 @@ import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) -class ScheduleServiceTest { +class ScheduleCommandServiceImplTest { @Mock private ClubRepository clubRepository; @@ -54,7 +52,7 @@ class ScheduleServiceTest { @Mock private ApplicationEventPublisher applicationEventPublisher; @InjectMocks - private ScheduleService scheduleService; + private ScheduleCommandServiceImpl scheduleCommandService; // 필요한 공동 객체 private ScheduleInput scheduleInput; @@ -68,7 +66,7 @@ void init() { .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) .minPeople(10) - .category("soccer") + .category("정기 운동") .note("note").build(); this.signupInput = SignupInput.builder().email("email").password("password") .name("name").birthday("birthday").gender(Gender.WOMAN.getKoreanName()).build(); @@ -77,10 +75,10 @@ void init() { .activityArea(ActivityArea.SEOUL.getKoreanName()).ageRange(AgeRange.TWENTIES.getKoreanName()).sportsType(SportsType.SOCCER.getKoreanName()) .clubPassword("clubPassword").profileImg(new MockMultipartFile("file", "file".getBytes())) .mainUniformColor("mainUniformColor").subUniformColor("subUniformColor").build(); - this.scheduleUpdateInput = ScheduleUpdateInput.builder().clubId(1L).id(1L).title("update title").location("update location") + this.scheduleUpdateInput = ScheduleUpdateInput.builder().clubId(1L).title("update title").location("update location") .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) - .minPeople(10).category("soccer").note("note").build(); + .minPeople(10).category("친선 매치").note("note").build(); } @Test @@ -95,12 +93,12 @@ void saveSchedule() { when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.of(userClub)); when(scheduleRepository.save(any(Schedule.class))).thenReturn(schedule); - ScheduleOutput scheduleOutput = scheduleService.saveSchedule(scheduleInput, user); + ScheduleOutput scheduleOutput = scheduleCommandService.saveSchedule(scheduleInput, user); //then assertThat(scheduleOutput.getTitle()).isEqualTo("title"); assertThat(scheduleOutput.getMinPeople()).isEqualTo(10); assertThat(scheduleOutput.getNote()).isEqualTo("note"); - verify(clubRepository, times(2)).findById(any(Long.class)); + verify(clubRepository, times(1)).findById(any(Long.class)); verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); verify(scheduleRepository, times(1)).save(any(Schedule.class)); verify(applicationEventPublisher, times(1)).publishEvent(any(ScheduleSaveEvent.class)); @@ -118,7 +116,7 @@ void saveSchedule_wrong_permission() { when(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.of(userClub)); //then Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.saveSchedule(scheduleInput, user); + scheduleCommandService.saveSchedule(scheduleInput, user); }); assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); verify(clubRepository, times(1)).findById(any(Long.class)); @@ -137,7 +135,7 @@ void updateSchedule() { when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - ScheduleOutput scheduleOutput = scheduleService.updateSchedule(scheduleUpdateInput, 1L, user); + ScheduleOutput scheduleOutput = scheduleCommandService.updateSchedule(scheduleUpdateInput, 1L, user); //then assertThat(scheduleOutput.getTitle()).isEqualTo("update title"); assertThat(scheduleOutput.getLocation()).isEqualTo("update location"); @@ -158,95 +156,13 @@ void updateSchedule_wrong_permission() { when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); //then Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.updateSchedule(scheduleUpdateInput, 1L, user); + scheduleCommandService.updateSchedule(scheduleUpdateInput, 1L, user); }); assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); verify(clubRepository, times(1)).findById(any(Long.class)); } - @Test - @DisplayName("한달 일정 조회하기") - void findSchedule() { - //given - Club club = Club.from(clubInput, null); - Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); - - //when - when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleService.findMonthSchedule(scheduleSearchInput); - - //then - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0).getTitle()).isEqualTo("title"); - assertThat(result.get(0).getNote()).isEqualTo("note"); - assertThat(result.get(0).getCategory()).isEqualTo("soccer"); - verify(clubRepository, times(1)).findById(any(Long.class)); - verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); - } - - @Test - @DisplayName("한달 일정이 없으면 빈 리스트를 반환한다") - void findSchedule_zero_schedule() { - //given - Club club = Club.from(clubInput, null); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); - - //when - when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of()); - List result = scheduleService.findMonthSchedule(scheduleSearchInput); - - //then - assertThat(result.size()).isEqualTo(0); - verify(clubRepository, times(1)).findById(any(Long.class)); - verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); - } - - @Test - @DisplayName("동아리의 하루 일정을 조회할 수 있다") - void findDaySchedule() { - //given - Club club = Club.from(clubInput, null); - Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); - - //when - when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleService.findDaySchedule(scheduleSearchInput); - - //then - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0).getTitle()).isEqualTo("title"); - assertThat(result.get(0).getNote()).isEqualTo("note"); - assertThat(result.get(0).getCategory()).isEqualTo("soccer"); - verify(clubRepository, times(1)).findById(any(Long.class)); - verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); - } - - @Test - @DisplayName("스케줄로 그 스케줄의 매치 신청 내역 등 자세한 정보를 볼 수 있다") - void findScheduleDetail() { - //given - Club club = Club.from(clubInput, null); - MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); - Schedule schedule = Schedule.from(club, scheduleInput); - schedule.setCreatedDate(); - schedule.setUpdatedDate(); - //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - when(matchApplicationRepository.findBySchedule(any(Schedule.class))).thenReturn(List.of(matchApplication)); - ScheduleDetailOutput result = scheduleService.findScheduleDetail(1L); - //then - assertThat(result.getMatchApplyClubList().size()).isEqualTo(1); - assertThat(result.getTitle()).isEqualTo("title"); - assertThat(result.getCategory()).isEqualTo("soccer"); - verify(scheduleRepository, times(1)).findById(any(Long.class)); - verify(matchApplicationRepository, times(1)).findBySchedule(any(Schedule.class)); - } @Test @DisplayName("멤버는 일정 참가에 대해 재투표를 할 수 있다") @@ -258,12 +174,12 @@ void voteSchedule_re() { User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "true"); //when - when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); - scheduleService.voteSchedule(scheduleVoteInput, user); + scheduleCommandService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); + verify(scheduleRepository, times(1)).findById(any(Long.class)); verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); } @@ -277,13 +193,13 @@ void voteSchedule() { User user = User.createUser(signupInput); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "false"); //when - when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); - scheduleService.voteSchedule(scheduleVoteInput, user); + scheduleCommandService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); + verify(scheduleRepository, times(1)).findById(any(Long.class)); verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); verify(scheduleVoteRepository, times(1)).save(any(ScheduleVote.class)); } @@ -294,7 +210,7 @@ void deleteSchedule() { //given Long id = 1L; //when - scheduleService.deleteSchedule(id); + scheduleCommandService.deleteSchedule(id); //then verify(scheduleRepository, times(1)).deleteById(any(Long.class)); } @@ -311,7 +227,7 @@ void voteEncourage() { //when when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); - scheduleService.voteEncourage(id); + scheduleCommandService.voteEncourage(id); //then verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(userClubRepository, times(1)).findUserByClub(any(Club.class)); @@ -327,11 +243,11 @@ void closeSchedule() { Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - scheduleService.closeSchedule(1L, user); + scheduleCommandService.closeSchedule(1L, user); //then - verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); } @@ -344,14 +260,14 @@ void closeSchedule_wrong_permission() { Schedule schedule = Schedule.from(club, scheduleInput); UserClub userClub = UserClub.createUserClub(user, club); //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); //then Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.closeSchedule(1L, user); + scheduleCommandService.closeSchedule(1L, user); }); assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); - verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); } } \ No newline at end of file diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java new file mode 100644 index 0000000..698571a --- /dev/null +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -0,0 +1,144 @@ +package com.example.moim.schedule.service; + +import com.example.moim.club.dto.request.ClubInput; +import com.example.moim.club.entity.Club; +import com.example.moim.club.repository.ClubRepository; +import com.example.moim.global.enums.*; +import com.example.moim.match.entity.Match; +import com.example.moim.match.entity.MatchApplication; +import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.schedule.dto.*; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.repository.ScheduleRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +public class ScheduleQueryServiceImplTest { + + @Mock + private ClubRepository clubRepository; + @Mock + private ScheduleRepository scheduleRepository; + @Mock + private MatchApplicationRepository matchApplicationRepository; + @InjectMocks + private ScheduleQueryServiceImpl scheduleQueryService; + + // 필요한 공동 객체 + private ScheduleInput scheduleInput; + private ClubInput clubInput; + + @BeforeEach + void init() { + this.scheduleInput = ScheduleInput.builder().clubId(1L).title("title").location("location") + .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) + .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) + .minPeople(10) + .category("정기 운동") + .note("note").build(); + this.clubInput = ClubInput.builder().title("title").explanation("explanation").introduction("introduction") + .clubCategory(ClubCategory.SMALL_GROUP.getKoreanName()).university("university").gender(Gender.UNISEX.getKoreanName()) + .activityArea(ActivityArea.SEOUL.getKoreanName()).ageRange(AgeRange.TWENTIES.getKoreanName()).sportsType(SportsType.SOCCER.getKoreanName()) + .clubPassword("clubPassword").profileImg(new MockMultipartFile("file", "file".getBytes())) + .mainUniformColor("mainUniformColor").subUniformColor("subUniformColor").build(); + } + + @Test + @DisplayName("한달 일정 조회하기") + void findSchedule() { + //given + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); + + //when + when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); + when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); + List result = scheduleQueryService.findMonthSchedule(scheduleSearchInput); + + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getTitle()).isEqualTo("title"); + assertThat(result.get(0).getNote()).isEqualTo("note"); + assertThat(result.get(0).getCategory()).isEqualTo("정기 운동"); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); + } + + @Test + @DisplayName("한달 일정이 없으면 빈 리스트를 반환한다") + void findSchedule_zero_schedule() { + //given + Club club = Club.from(clubInput, null); + ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); + + //when + when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); + when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of()); + List result = scheduleQueryService.findMonthSchedule(scheduleSearchInput); + + //then + assertThat(result.size()).isEqualTo(0); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); + } + + @Test + @DisplayName("동아리의 하루 일정을 조회할 수 있다") + void findDaySchedule() { + //given + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); + + //when + when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); + when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); + List result = scheduleQueryService.findDaySchedule(scheduleSearchInput); + + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getTitle()).isEqualTo("title"); + assertThat(result.get(0).getNote()).isEqualTo("note"); + assertThat(result.get(0).getCategory()).isEqualTo("정기 운동"); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class)); + } + + @Test + @DisplayName("스케줄로 그 스케줄의 매치 신청 내역 등 자세한 정보를 볼 수 있다") + void findScheduleDetail() { + //given + Club club = Club.from(clubInput, null); + MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); + Schedule schedule = Schedule.from(club, scheduleInput); + schedule.setCreatedDate(); + schedule.setUpdatedDate(); + //when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(matchApplicationRepository.findBySchedule(any(Schedule.class))).thenReturn(List.of(matchApplication)); + ScheduleDetailOutput result = scheduleQueryService.findScheduleDetail(1L); + //then + assertThat(result.getMatchApplyClubList().size()).isEqualTo(1); + assertThat(result.getTitle()).isEqualTo("title"); + assertThat(result.getCategory()).isEqualTo("정기 운동"); + verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(matchApplicationRepository, times(1)).findBySchedule(any(Schedule.class)); + } +} diff --git a/src/test/resources/sql/comment-repository-test-data.sql b/src/test/resources/sql/comment-repository-test-data.sql index 02d19cd..9ef9177 100644 --- a/src/test/resources/sql/comment-repository-test-data.sql +++ b/src/test/resources/sql/comment-repository-test-data.sql @@ -26,9 +26,9 @@ INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, mat VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성 insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'soccor', 'note', 10, 12, 0); +values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'soccor', 'note', 10, 12, 0); +values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -- 댓글 생성 insert into `comment` (id, user_id, schedule_id, contents) values (1, 3, 1, '회사 면접이 잡혀있어서 못 갑니다'); diff --git a/src/test/resources/sql/schedule-vote-repository-test-data.sql b/src/test/resources/sql/schedule-vote-repository-test-data.sql index d3b8158..2387c60 100644 --- a/src/test/resources/sql/schedule-vote-repository-test-data.sql +++ b/src/test/resources/sql/schedule-vote-repository-test-data.sql @@ -23,9 +23,9 @@ INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, mat VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성 insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'soccor', 'note', 10, 12, 0); +values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'soccor', 'note', 10, 12, 0); +values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -- 댓글 생성 insert into `comment` (id, user_id, schedule_id, contents) values (1, 3, 1, '회사 면접이 잡혀있어서 못 갑니다'); From 7b8f6504a69eb2e12a684967579a2088d4dbc442 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 14 Apr 2025 17:38:25 +0900 Subject: [PATCH 09/33] =?UTF-8?q?refactor(#33):=20schedule,=20vote,=20comm?= =?UTF-8?q?ent=20=EB=B6=84=EB=A6=AC.=20schedule=20=EC=97=90=EC=84=9C=20vot?= =?UTF-8?q?e=20=EC=B2=98=EB=A6=AC=ED=95=98=EB=8A=94=20=EA=B2=83.=20Attenda?= =?UTF-8?q?nceType=20=EC=9D=84=20enum=20=EC=9C=BC=EB=A1=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=98=EC=97=AC=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B0=92=20=EB=B0=A9=EC=A7=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/global/exception/ResponseCode.java | 3 +- .../example/moim/match/entity/MatchUser.java | 2 +- .../moim/match/service/MatchService.java | 10 +- .../{ => comment}/entity/Comment.java | 3 +- .../repository/CommentRepository.java | 4 +- .../controller/ScheduleController.java | 10 -- .../controller/ScheduleControllerDocs.java | 3 - .../moim/schedule/dto/CommentOutput.java | 2 +- .../moim/schedule/dto/ScheduleUserOutput.java | 6 +- .../moim/schedule/entity/Schedule.java | 26 +++- .../service/ScheduleCommandService.java | 2 - .../service/ScheduleCommandServiceImpl.java | 46 +----- .../controller/ScheduleVoteController.java | 23 ++- .../ScheduleVoteControllerDocs.java | 16 ++ .../schedule/vote/entity/AttendanceType.java | 28 ++++ .../{ => vote}/entity/ScheduleVote.java | 13 +- .../repository/ScheduleVoteRepository.java | 4 +- .../vote/service/ScheduleVoteService.java | 9 ++ .../vote/service/ScheduleVoteServiceImpl.java | 74 ++++++++++ .../example/moim/match/MatchServiceTest.java | 2 +- .../repository/CommentRepositoryTest.java | 3 +- .../ScheduleVoteRepositoryTest.java | 3 +- .../ScheduleCommandServiceImplTest.java | 68 +-------- .../service/ScheduleVoteServiceImplTest.java | 139 ++++++++++++++++++ .../schedule-vote-repository-test-data.sql | 8 +- 25 files changed, 347 insertions(+), 160 deletions(-) rename src/main/java/com/example/moim/schedule/{ => comment}/entity/Comment.java (88%) rename src/main/java/com/example/moim/schedule/{ => comment}/repository/CommentRepository.java (82%) create mode 100644 src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java create mode 100644 src/main/java/com/example/moim/schedule/vote/entity/AttendanceType.java rename src/main/java/com/example/moim/schedule/{ => vote}/entity/ScheduleVote.java (71%) rename src/main/java/com/example/moim/schedule/{ => vote}/repository/ScheduleVoteRepository.java (89%) create mode 100644 src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java create mode 100644 src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java create mode 100644 src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index f700d29..359b247 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -77,7 +77,8 @@ public enum ResponseCode { INVALID_MAIN_FOOT(HttpStatus.BAD_REQUEST, "ENUM4006", "유효하지 않은 주발입니다."), INVALID_SPORTS_TYPE(HttpStatus.BAD_REQUEST, "ENUM4007", "유효하지 않은 종목입니다."), INVALID_CLUB_ROLE(HttpStatus.BAD_REQUEST, "ENUM4008", "유효하지 않은 모임 역할입니다."), - INVALID_SCHEDULE_CATEGORY(HttpStatus.BAD_REQUEST, "ENUM4009", "유효하지 않은 일정 종류입니다."); + INVALID_SCHEDULE_CATEGORY(HttpStatus.BAD_REQUEST, "ENUM4009", "유효하지 않은 일정 종류입니다."), + INVALID_ATTENDANCE_TYPE(HttpStatus.BAD_REQUEST, "ENUM4010", "유효하지 않은 투표입니다.(참석/불참)"); private final HttpStatus httpStatus; private final String code; diff --git a/src/main/java/com/example/moim/match/entity/MatchUser.java b/src/main/java/com/example/moim/match/entity/MatchUser.java index d563019..89de4b6 100644 --- a/src/main/java/com/example/moim/match/entity/MatchUser.java +++ b/src/main/java/com/example/moim/match/entity/MatchUser.java @@ -1,7 +1,7 @@ package com.example.moim.match.entity; import com.example.moim.club.entity.Club; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.club.entity.UserClub; import com.example.moim.match.dto.MatchRecordInput; import com.example.moim.user.entity.User; diff --git a/src/main/java/com/example/moim/match/service/MatchService.java b/src/main/java/com/example/moim/match/service/MatchService.java index 442bca5..84cf6ed 100644 --- a/src/main/java/com/example/moim/match/service/MatchService.java +++ b/src/main/java/com/example/moim/match/service/MatchService.java @@ -7,11 +7,11 @@ import com.example.moim.notification.dto.MatchCancelClubEvent; import com.example.moim.notification.dto.MatchCancelUserEvent; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.ClubRepository; import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.repository.ScheduleVoteRepository; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.schedule.service.ScheduleCommandServiceImpl; import com.example.moim.match.dto.*; @@ -134,7 +134,7 @@ public String cancelMatch(User user, Long matchId) { if(match.getMatchStatus() == MatchStatus.PENDING) { // 생성 대기 상태 취소 List votes = scheduleVoteRepository.findBySchedule(match.getSchedule()); for(ScheduleVote vote : votes) { - if("attend".equals(vote.getAttendance())) { + if("attend".equals(vote.getIsAttendance())) { // 홈 팀 알림 발송 eventPublisher.publishEvent(new MatchCancelUserEvent(match, vote.getUser())); } @@ -151,7 +151,7 @@ public String cancelMatch(User user, Long matchId) { List votes = scheduleVoteRepository.findBySchedule(match.getSchedule()); for(ScheduleVote vote : votes) { - if("attend".equals(vote.getAttendance())) { + if("attend".equals(vote.getIsAttendance())) { // 홈 팀 알림 발송 eventPublisher.publishEvent(new MatchCancelUserEvent(match, vote.getUser())); } @@ -303,7 +303,7 @@ public MatchConfirmOutput confirmMatch(Long id, Long awayClubId, User user) { //유저가 친선 매치 일정에 참여 투표 시 매치 유저 저장, 수정 필요 -> 문제 있나? private void saveMatchUserByAttendance(Match match, Schedule schedule) { for (ScheduleVote scheduleVote : scheduleVoteRepository.findBySchedule(schedule)) { - if (scheduleVote.getAttendance().equals("attend")) { + if (scheduleVote.getIsAttendance().equals("attend")) { log.info("userid:{}", scheduleVote.getUser().getId()); MatchUser matchUser = MatchUser.createMatchUser(match, scheduleVote); matchUserRepository.save(matchUser); diff --git a/src/main/java/com/example/moim/schedule/entity/Comment.java b/src/main/java/com/example/moim/schedule/comment/entity/Comment.java similarity index 88% rename from src/main/java/com/example/moim/schedule/entity/Comment.java rename to src/main/java/com/example/moim/schedule/comment/entity/Comment.java index f2d4c0d..ff2a715 100644 --- a/src/main/java/com/example/moim/schedule/entity/Comment.java +++ b/src/main/java/com/example/moim/schedule/comment/entity/Comment.java @@ -1,6 +1,7 @@ -package com.example.moim.schedule.entity; +package com.example.moim.schedule.comment.entity; import com.example.moim.global.entity.BaseEntity; +import com.example.moim.schedule.entity.Schedule; import com.example.moim.user.entity.User; import jakarta.persistence.*; import lombok.Getter; diff --git a/src/main/java/com/example/moim/schedule/repository/CommentRepository.java b/src/main/java/com/example/moim/schedule/comment/repository/CommentRepository.java similarity index 82% rename from src/main/java/com/example/moim/schedule/repository/CommentRepository.java rename to src/main/java/com/example/moim/schedule/comment/repository/CommentRepository.java index 8db39bc..f90e2f0 100644 --- a/src/main/java/com/example/moim/schedule/repository/CommentRepository.java +++ b/src/main/java/com/example/moim/schedule/comment/repository/CommentRepository.java @@ -1,6 +1,6 @@ -package com.example.moim.schedule.repository; +package com.example.moim.schedule.comment.repository; -import com.example.moim.schedule.entity.Comment; +import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.entity.Schedule; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 5399ef7..d4f6de2 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -48,16 +48,6 @@ public void scheduleDelete(@PathVariable Long id) { scheduleCommandService.deleteSchedule(id); } - @PatchMapping("/schedules/vote") - public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleCommandService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); - } - - @PostMapping("/schedules/encourage/{id}") - public void voteEncourage(@PathVariable Long id) { - scheduleCommandService.voteEncourage(id); - } - @PatchMapping("/schedules/close/{id}") public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { scheduleCommandService.closeSchedule(id, userDetailsImpl.getUser()); diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index d99aef0..c995f11 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -36,9 +36,6 @@ public interface ScheduleControllerDocs { @Operation(summary = "일정 삭제") void scheduleDelete(@PathVariable Long id); - @Operation(summary = "일정 참가 투표", description = "참가면 attendance = attend, 참가 취소는 absent") - void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); - @Operation(summary = "일정 참가 투표 마감") void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); diff --git a/src/main/java/com/example/moim/schedule/dto/CommentOutput.java b/src/main/java/com/example/moim/schedule/dto/CommentOutput.java index c24ea0f..676ef0b 100644 --- a/src/main/java/com/example/moim/schedule/dto/CommentOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/CommentOutput.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.dto; -import com.example.moim.schedule.entity.Comment; +import com.example.moim.schedule.comment.entity.Comment; import lombok.Data; @Data diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleUserOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleUserOutput.java index b474c78..b30854d 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleUserOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleUserOutput.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.dto; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import lombok.Data; import org.springframework.core.io.FileUrlResource; @@ -22,8 +22,8 @@ public ScheduleUserOutput(ScheduleVote scheduleVote) { throw new RuntimeException(e); } } - if (scheduleVote.getAttendance() != null) { - this.attendance = scheduleVote.getAttendance(); + if (scheduleVote.getIsAttendance() != null) { + this.attendance = scheduleVote.getIsAttendance()? "참가" : "불참"; } else { this.attendance = "notVote"; } diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 724a70e..c51e6bf 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -2,10 +2,12 @@ import com.example.moim.club.entity.Club; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.dto.ScheduleInput; import com.example.moim.schedule.dto.ScheduleUpdateInput; import com.example.moim.global.entity.BaseEntity; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; +import com.example.moim.schedule.vote.entity.AttendanceType; import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; @@ -56,23 +58,29 @@ public static Schedule from(Club club, ScheduleInput scheduleInput) { return schedule; } + /** + * FIXME: 뭔가 지금 이 구조가 이상함. ScheduleVote 에서의 역할이 자꾸 여기서 처리되는듯. 이걸 ScheduleVoteService 에서 Boolean 값으로 넘겨주는게 맞는 듯 + * @param attendance + */ public void vote(String attendance) { - if (attendance.equals("attend")) { + if (getAttendanceType(attendance).equals(AttendanceType.ATTEND)) { this.attend += 1; - } else if (attendance.equals("absent")) { + } else { this.nonAttend += 1; } } - public void reVote(String originalAttendance, String attendance) { - if (originalAttendance.equals("attend")) { + public void reVote(boolean originalAttendance, boolean isAttendance) { + // true - 참석 / false - 불참 + if (originalAttendance) { this.attend -= 1; - } else if (originalAttendance.equals("absent")) { + } else { this.nonAttend -= 1; } - if (attendance.equals("attend")) { + + if (isAttendance) { this.attend += 1; - } else if (attendance.equals("absent")) { + } else { this.nonAttend += 1; } } @@ -105,4 +113,8 @@ public void close() { this.isClose = true; } + private AttendanceType getAttendanceType(String attendance) { + return AttendanceType.fromKoreanName(attendance).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_ATTENDANCE_TYPE)); + } + } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java index bfa1eb0..04bd1bc 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java @@ -6,9 +6,7 @@ public interface ScheduleCommandService { ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user); ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user); - void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); void deleteSchedule(Long id); - void voteEncourage(Long id); void closeSchedule(Long id, User user); void saveComment(CommentInput commentInput, User user); diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java index 19fbce8..2a64299 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -8,13 +8,13 @@ import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; import com.example.moim.schedule.dto.*; -import com.example.moim.schedule.entity.Comment; +import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; -import com.example.moim.schedule.repository.CommentRepository; +import com.example.moim.schedule.comment.repository.CommentRepository; import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.repository.ScheduleVoteRepository; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import com.example.moim.user.entity.User; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -34,7 +34,6 @@ public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final UserClubRepository userClubRepository; private final CommentRepository commentRepository; private final ApplicationEventPublisher eventPublisher; - private final ScheduleVoteRepository scheduleVoteRepository; private final MatchApplicationRepository matchApplicationRepository; public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { @@ -75,32 +74,6 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo return new ScheduleOutput(schedule); } - /** - * TODO: void -> 기본 응답 - * @param scheduleVoteInput - * @param user - */ - @Transactional - public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { - Schedule schedule = getSchedule(scheduleVoteInput.getId()); - Optional originalScheduleVote = scheduleVoteRepository.findByScheduleAndUser(schedule, user); - - //투표 처음이면 - if (originalScheduleVote.isEmpty()) { - scheduleVoteRepository.save(ScheduleVote.createScheduleVote(user, schedule, scheduleVoteInput.getAttendance())); - schedule.vote(scheduleVoteInput.getAttendance()); - } else { // 재투표인 경우 - schedule.reVote(originalScheduleVote.get().getAttendance(), scheduleVoteInput.getAttendance()); - originalScheduleVote.get().changeAttendance(scheduleVoteInput.getAttendance()); - } - /** - * TODO: 왜 알림 보내려고 했는지 확인하고 로직 추가하기 - */ -// if (scheduleVoteInput.getAttendance().equals("attend")) { -// eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); -// } - } - /** * TODO: void -> 기본 응답 * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) @@ -110,17 +83,6 @@ public void deleteSchedule(Long id) { scheduleRepository.deleteById(id); } - /** - * TODO: void -> 기본 응답 - * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) - * @param id - */ - public void voteEncourage(Long id) { - Schedule schedule = scheduleRepository.findWithClubById(id); - List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); - eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); - } - /** * TODO: void -> 기본 응답, 이름 명확하게 바꾸기 closeScheduleVote 등 * @param id diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index 6b65cf1..77d76d8 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -1,4 +1,25 @@ package com.example.moim.schedule.vote.controller; -public class ScheduleVoteController { +import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.service.ScheduleVoteService; +import com.example.moim.user.dto.UserDetailsImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +public class ScheduleVoteController implements ScheduleVoteControllerDocs { + + private ScheduleVoteService scheduleVoteService; + + @PatchMapping("/schedules/vote") + public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + scheduleVoteService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + } + + @PostMapping("/schedules/encourage/{id}") + public void voteEncourage(@PathVariable Long id) { + scheduleVoteService.voteEncourage(id); + } } diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java new file mode 100644 index 0000000..1febb58 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -0,0 +1,16 @@ +package com.example.moim.schedule.vote.controller; + +import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.user.dto.UserDetailsImpl; +import io.swagger.v3.oas.annotations.Operation; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; + +public interface ScheduleVoteControllerDocs { + @Operation(summary = "일정 참가 투표", description = "참가면 attendance = attend, 참가 취소는 absent") + void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + + @Operation(summary = "일정 참가 투표 독려") + void voteEncourage(@PathVariable Long id); +} diff --git a/src/main/java/com/example/moim/schedule/vote/entity/AttendanceType.java b/src/main/java/com/example/moim/schedule/vote/entity/AttendanceType.java new file mode 100644 index 0000000..ce307e8 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/entity/AttendanceType.java @@ -0,0 +1,28 @@ +package com.example.moim.schedule.vote.entity; + +import java.util.Arrays; +import java.util.Optional; + +public enum AttendanceType { + ATTEND("참석", true), ABSENT("불참", false); + private final String koreanName; + private final Boolean isAttendance; + + AttendanceType(String koreanName, boolean isAttendance) { + this.koreanName = koreanName; + this.isAttendance = isAttendance; + } + + public String getKoreanName() { + return koreanName; + } + public boolean getIsAttendance() { + return isAttendance; + } + + public static Optional fromKoreanName(String koreanName) { + return Arrays.stream(values()) + .filter(g -> g.getKoreanName().equals(koreanName)) + .findFirst(); + } +} diff --git a/src/main/java/com/example/moim/schedule/entity/ScheduleVote.java b/src/main/java/com/example/moim/schedule/vote/entity/ScheduleVote.java similarity index 71% rename from src/main/java/com/example/moim/schedule/entity/ScheduleVote.java rename to src/main/java/com/example/moim/schedule/vote/entity/ScheduleVote.java index 70b3e56..8f011c8 100644 --- a/src/main/java/com/example/moim/schedule/entity/ScheduleVote.java +++ b/src/main/java/com/example/moim/schedule/vote/entity/ScheduleVote.java @@ -1,6 +1,7 @@ -package com.example.moim.schedule.entity; +package com.example.moim.schedule.vote.entity; import com.example.moim.global.entity.BaseEntity; +import com.example.moim.schedule.entity.Schedule; import com.example.moim.user.entity.User; import jakarta.persistence.*; import lombok.Getter; @@ -19,17 +20,17 @@ public class ScheduleVote extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "schedule_id") private Schedule schedule; - private String attendance; + private Boolean isAttendance; - public static ScheduleVote createScheduleVote(User user, Schedule schedule, String attendance) { + public static ScheduleVote createScheduleVote(User user, Schedule schedule, boolean isAttendance) { ScheduleVote scheduleVote = new ScheduleVote(); scheduleVote.user = user; scheduleVote.schedule = schedule; - scheduleVote.attendance = attendance; + scheduleVote.isAttendance = isAttendance; return scheduleVote; } - public void changeAttendance(String attendance) { - this.attendance = attendance; + public void changeAttendance(boolean isAttendance) { + this.isAttendance = isAttendance; } } diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleVoteRepository.java b/src/main/java/com/example/moim/schedule/vote/repository/ScheduleVoteRepository.java similarity index 89% rename from src/main/java/com/example/moim/schedule/repository/ScheduleVoteRepository.java rename to src/main/java/com/example/moim/schedule/vote/repository/ScheduleVoteRepository.java index 134ff35..dc3a8ec 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleVoteRepository.java +++ b/src/main/java/com/example/moim/schedule/vote/repository/ScheduleVoteRepository.java @@ -1,7 +1,7 @@ -package com.example.moim.schedule.repository; +package com.example.moim.schedule.vote.repository; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java new file mode 100644 index 0000000..9230fb4 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -0,0 +1,9 @@ +package com.example.moim.schedule.vote.service; + +import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.user.entity.User; + +public interface ScheduleVoteService { + void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); + void voteEncourage(Long id); +} diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java new file mode 100644 index 0000000..d849c20 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -0,0 +1,74 @@ +package com.example.moim.schedule.vote.service; + +import com.example.moim.club.entity.UserClub; +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.notification.dto.ScheduleEncourageEvent; +import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; +import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.schedule.vote.entity.AttendanceType; +import com.example.moim.schedule.vote.entity.ScheduleVote; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; +import com.example.moim.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class ScheduleVoteServiceImpl implements ScheduleVoteService { + + private final ScheduleRepository scheduleRepository; + private final ScheduleVoteRepository scheduleVoteRepository; + private final UserClubRepository userClubRepository; + private final ApplicationEventPublisher eventPublisher; + + /** + * TODO: void -> 기본 응답 + * @param scheduleVoteInput + * @param user + */ + @Transactional + public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { + Schedule schedule = getSchedule(scheduleVoteInput.getId()); + Optional originalScheduleVote = scheduleVoteRepository.findByScheduleAndUser(schedule, user); + + boolean isAttendance = AttendanceType.fromKoreanName(scheduleVoteInput.getAttendance()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_ATTENDANCE_TYPE)).getIsAttendance(); + + //투표 처음이면 + if (originalScheduleVote.isEmpty()) { + scheduleVoteRepository.save(ScheduleVote.createScheduleVote(user, schedule, isAttendance)); + schedule.vote(scheduleVoteInput.getAttendance()); + } else { // 재투표인 경우 + schedule.reVote(originalScheduleVote.get().getIsAttendance(), isAttendance); + originalScheduleVote.get().changeAttendance(isAttendance); + } + /** + * TODO: 왜 알림 보내려고 했는지 확인하고 로직 추가하기 + */ +// if (scheduleVoteInput.getAttendance().equals("attend")) { +// eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); +// } + } + + /** + * TODO: void -> 기본 응답 + * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) + * @param id + */ + public void voteEncourage(Long id) { + Schedule schedule = scheduleRepository.findWithClubById(id); + List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); + eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); + } + + private Schedule getSchedule(Long scheduleId) { + return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); + } +} diff --git a/src/test/java/com/example/moim/match/MatchServiceTest.java b/src/test/java/com/example/moim/match/MatchServiceTest.java index 4b4085b..6e99751 100644 --- a/src/test/java/com/example/moim/match/MatchServiceTest.java +++ b/src/test/java/com/example/moim/match/MatchServiceTest.java @@ -17,7 +17,7 @@ import com.example.moim.notification.dto.MatchRequestEvent; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.repository.ScheduleVoteRepository; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import com.example.moim.user.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/moim/schedule/repository/CommentRepositoryTest.java b/src/test/java/com/example/moim/schedule/repository/CommentRepositoryTest.java index 5eea562..b80cc45 100644 --- a/src/test/java/com/example/moim/schedule/repository/CommentRepositoryTest.java +++ b/src/test/java/com/example/moim/schedule/repository/CommentRepositoryTest.java @@ -1,6 +1,7 @@ package com.example.moim.schedule.repository; -import com.example.moim.schedule.entity.Comment; +import com.example.moim.schedule.comment.entity.Comment; +import com.example.moim.schedule.comment.repository.CommentRepository; import com.example.moim.schedule.entity.Schedule; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java index b9aca18..fbb1281 100644 --- a/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java +++ b/src/test/java/com/example/moim/schedule/repository/ScheduleVoteRepositoryTest.java @@ -1,7 +1,8 @@ package com.example.moim.schedule.repository; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java index d83508a..c3ae122 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java @@ -11,10 +11,10 @@ import com.example.moim.notification.dto.ScheduleSaveEvent; import com.example.moim.schedule.dto.*; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; +import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.repository.ScheduleVoteRepository; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import com.example.moim.user.dto.SignupInput; import com.example.moim.user.entity.User; import org.junit.jupiter.api.BeforeEach; @@ -46,10 +46,6 @@ class ScheduleCommandServiceImplTest { @Mock private UserClubRepository userClubRepository; @Mock - private ScheduleVoteRepository scheduleVoteRepository; - @Mock - private MatchApplicationRepository matchApplicationRepository; - @Mock private ApplicationEventPublisher applicationEventPublisher; @InjectMocks private ScheduleCommandServiceImpl scheduleCommandService; @@ -163,47 +159,6 @@ void updateSchedule_wrong_permission() { verify(clubRepository, times(1)).findById(any(Long.class)); } - - @Test - @DisplayName("멤버는 일정 참가에 대해 재투표를 할 수 있다") - void voteSchedule_re() { - //given - Club club = Club.from(clubInput, null); - Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); - User user = User.createUser(signupInput); - ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "true"); - //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); - scheduleCommandService.voteSchedule(scheduleVoteInput, user); - //then - assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findById(any(Long.class)); - verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); - } - - @Test - @DisplayName("멤버는 일정 참가에 대해 투표를 할 수 있다") - void voteSchedule() { - //given - Club club = Club.from(clubInput, null); - Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("false").build(); - User user = User.createUser(signupInput); - ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, "false"); - //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); - when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); - scheduleCommandService.voteSchedule(scheduleVoteInput, user); - //then - assertThat(scheduleVote.getAttendance()).isEqualTo("false"); - verify(scheduleRepository, times(1)).findById(any(Long.class)); - verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); - verify(scheduleVoteRepository, times(1)).save(any(ScheduleVote.class)); - } - @Test @DisplayName("일정을 삭제할 수 있다") void deleteSchedule() { @@ -215,25 +170,6 @@ void deleteSchedule() { verify(scheduleRepository, times(1)).deleteById(any(Long.class)); } - @Test - @DisplayName("운영진은 투표를 독려할 수 있다") - void voteEncourage() { - //given - Long id = 1L; - Club club = Club.from(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.from(club, scheduleInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - //when - when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); - when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); - scheduleCommandService.voteEncourage(id); - //then - verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); - verify(userClubRepository, times(1)).findUserByClub(any(Club.class)); - verify(applicationEventPublisher, times(1)).publishEvent(any(ScheduleEncourageEvent.class)); - } - @Test @DisplayName("운영진은 일정 투표를 마감할 수 있다") void closeSchedule() { diff --git a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java new file mode 100644 index 0000000..db4f29d --- /dev/null +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -0,0 +1,139 @@ +package com.example.moim.schedule.vote.service; + +import com.example.moim.club.dto.request.ClubInput; +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.UserClub; +import com.example.moim.club.repository.ClubRepository; +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.*; +import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.notification.dto.ScheduleEncourageEvent; +import com.example.moim.schedule.dto.ScheduleInput; +import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.schedule.vote.entity.ScheduleVote; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; +import com.example.moim.user.dto.SignupInput; +import com.example.moim.user.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.mock.web.MockMultipartFile; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +class ScheduleVoteServiceImplTest { + + @Mock + private ClubRepository clubRepository; + @Mock + private ScheduleRepository scheduleRepository; + @Mock + private UserClubRepository userClubRepository; + @Mock + private ScheduleVoteRepository scheduleVoteRepository; + @Mock + private MatchApplicationRepository matchApplicationRepository; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + @InjectMocks + private ScheduleVoteServiceImpl scheduleVoteService; + + // 필요한 공동 객체 + private ScheduleInput scheduleInput; + private SignupInput signupInput; + private ClubInput clubInput; + + @BeforeEach + void init() { + this.scheduleInput = ScheduleInput.builder().clubId(1L).title("title").location("location") + .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) + .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) + .minPeople(10) + .category("정기 운동") + .note("note").build(); + this.signupInput = SignupInput.builder().email("email").password("password") + .name("name").birthday("birthday").gender(Gender.WOMAN.getKoreanName()).build(); + this.clubInput = ClubInput.builder().title("title").explanation("explanation").introduction("introduction") + .clubCategory(ClubCategory.SMALL_GROUP.getKoreanName()).university("university").gender(Gender.UNISEX.getKoreanName()) + .activityArea(ActivityArea.SEOUL.getKoreanName()).ageRange(AgeRange.TWENTIES.getKoreanName()).sportsType(SportsType.SOCCER.getKoreanName()) + .clubPassword("clubPassword").profileImg(new MockMultipartFile("file", "file".getBytes())) + .mainUniformColor("mainUniformColor").subUniformColor("subUniformColor").build(); + } + + + @Test + @DisplayName("멤버는 일정 참가에 대해 재투표를 할 수 있다") + void voteSchedule_re() { + //given + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); + User user = User.createUser(signupInput); + ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, true); + //when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); + scheduleVoteService.voteSchedule(scheduleVoteInput, user); + //then + assertThat(scheduleVote.getIsAttendance()).isFalse(); + verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); + } + + @Test + @DisplayName("멤버는 일정 참가에 대해 투표를 할 수 있다") + void voteSchedule() { + //given + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); + User user = User.createUser(signupInput); + ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, false); + //when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); + when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); + scheduleVoteService.voteSchedule(scheduleVoteInput, user); + //then + assertThat(scheduleVote.getIsAttendance()).isFalse(); + verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); + verify(scheduleVoteRepository, times(1)).save(any(ScheduleVote.class)); + } + + @Test + @DisplayName("운영진은 투표를 독려할 수 있다") + void voteEncourage() { + //given + Long id = 1L; + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + Schedule schedule = Schedule.from(club, scheduleInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + //when + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); + scheduleVoteService.voteEncourage(id); + //then + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); + verify(userClubRepository, times(1)).findUserByClub(any(Club.class)); + verify(applicationEventPublisher, times(1)).publishEvent(any(ScheduleEncourageEvent.class)); + } + + +} \ No newline at end of file diff --git a/src/test/resources/sql/schedule-vote-repository-test-data.sql b/src/test/resources/sql/schedule-vote-repository-test-data.sql index 2387c60..f994d2d 100644 --- a/src/test/resources/sql/schedule-vote-repository-test-data.sql +++ b/src/test/resources/sql/schedule-vote-repository-test-data.sql @@ -30,7 +30,7 @@ values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14: insert into `comment` (id, user_id, schedule_id, contents) values (1, 3, 1, '회사 면접이 잡혀있어서 못 갑니다'); -- 투표 생성 -insert into `schedule_vote` (id, user_id, schedule_id, attendance) -values (1, 3, 1, 'true'); -insert into `schedule_vote` (id, user_id, schedule_id, attendance) -values (2, 4, 1, 'true'); +insert into `schedule_vote` (id, user_id, schedule_id, is_attendance) +values (1, 3, 1, 1); +insert into `schedule_vote` (id, user_id, schedule_id, is_attendance) +values (2, 4, 1, 1); From 52ce3b5664fdec2074fdcd6f94cabe8599cc3029 Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 17 Apr 2025 17:08:11 +0900 Subject: [PATCH 10/33] =?UTF-8?q?refactor(#33):=20Match.createScheduleFrom?= =?UTF-8?q?Match=20=EB=A9=94=EC=84=9C=EB=93=9C=EC=97=90=20ScheduleCategory?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/moim/match/entity/Match.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/moim/match/entity/Match.java b/src/main/java/com/example/moim/match/entity/Match.java index ab81b01..1771b7b 100644 --- a/src/main/java/com/example/moim/match/entity/Match.java +++ b/src/main/java/com/example/moim/match/entity/Match.java @@ -12,6 +12,7 @@ import com.example.moim.global.entity.BaseEntity; import com.example.moim.match.dto.MatchInput; import com.example.moim.match.dto.MatchRegInput; +import com.example.moim.schedule.entity.ScheduleCategory; import jakarta.persistence.*; import lombok.Getter; @@ -143,7 +144,7 @@ public ScheduleInput createScheduleFromMatch() { getStartTime(), getEndTime(), getMinParticipants(), - "친선 매치", + ScheduleCategory.FRIENDLY_MATCH.getKoreanName(), getNote()); } From 5ecfa371ce9fdcd951cec92587ad7e9037c0a70e Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 17 Apr 2025 17:27:41 +0900 Subject: [PATCH 11/33] =?UTF-8?q?refactor(#33):=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=20=EB=8C=93=EA=B8=80=20=EA=B8=B0=EB=8A=A5=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleCommentController.java | 19 ++++++++++++ .../ScheduleCommentCommandService.java | 8 +++++ .../ScheduleCommentCommandServiceImpl.java | 30 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java create mode 100644 src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java index 50304eb..555ebb8 100644 --- a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java @@ -1,4 +1,23 @@ package com.example.moim.schedule.comment.controller; +import com.example.moim.schedule.comment.service.ScheduleCommentCommandService; +import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.user.dto.UserDetailsImpl; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor public class ScheduleCommentController { + + private final ScheduleCommentCommandService scheduleCommentCommandService; + + @PostMapping("/schedules/{id}/comments") + public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + scheduleCommentCommandService.saveComment(commentInput, userDetailsImpl.getUser()); + } } diff --git a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java new file mode 100644 index 0000000..ba8a647 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java @@ -0,0 +1,8 @@ +package com.example.moim.schedule.comment.service; + +import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.user.entity.User; + +public interface ScheduleCommentCommandService { + void saveComment(CommentInput commentInput, User user); +} diff --git a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java new file mode 100644 index 0000000..4697b03 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java @@ -0,0 +1,30 @@ +package com.example.moim.schedule.comment.service; + +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.schedule.comment.entity.Comment; +import com.example.moim.schedule.comment.repository.CommentRepository; +import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; +import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ScheduleCommentCommandServiceImpl implements ScheduleCommentCommandService { + + private final CommentRepository commentRepository; + private final ScheduleRepository scheduleRepository; + + public void saveComment(CommentInput commentInput, User user) { + Schedule schedule = getSchedule(commentInput.getId()); + commentRepository.save(Comment.createComment(user, schedule, commentInput.getContents())); + } + + private Schedule getSchedule(Long scheduleId) { + return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); + } + +} From 1a8bcda97c36a2be5aa4bedf658b9a3c55d9d06a Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 17 Apr 2025 22:15:05 +0900 Subject: [PATCH 12/33] =?UTF-8?q?refactor(#33):=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=20=EB=8F=84=EB=A9=94=EC=9D=B8=20Controller=20?= =?UTF-8?q?=EB=93=A4=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD.=20-=20?= =?UTF-8?q?=EB=8F=99=EC=82=AC=EB=A1=9C=20=EC=8B=9C=EC=9E=91,=20get,=20crea?= =?UTF-8?q?te,=20update,=20delete=20=EB=93=B1=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/main/service/MainService.java | 2 +- .../ScheduleCommentControllerDocs.java | 17 +++++++ .../controller/ScheduleController.java | 26 ++++------- .../controller/ScheduleControllerDocs.java | 16 +++---- .../service/ScheduleCommandService.java | 3 -- .../service/ScheduleCommandServiceImpl.java | 21 --------- .../service/ScheduleQueryService.java | 4 +- .../service/ScheduleQueryServiceImpl.java | 4 +- .../controller/ScheduleVoteController.java | 11 +++-- .../ScheduleVoteControllerDocs.java | 8 +++- .../vote/service/ScheduleVoteService.java | 2 + .../vote/service/ScheduleVoteServiceImpl.java | 21 +++++++++ .../ScheduleCommandServiceImplTest.java | 42 ----------------- .../service/ScheduleQueryServiceImplTest.java | 6 +-- .../service/ScheduleVoteServiceImplTest.java | 45 +++++++++++++++++-- 15 files changed, 117 insertions(+), 111 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java diff --git a/src/main/java/com/example/moim/main/service/MainService.java b/src/main/java/com/example/moim/main/service/MainService.java index d4bb698..6080845 100644 --- a/src/main/java/com/example/moim/main/service/MainService.java +++ b/src/main/java/com/example/moim/main/service/MainService.java @@ -27,7 +27,7 @@ public NoClubMainOutput noClubMainPage (User user) { public MainOutput mainPage(Long clubId) { return new MainOutput(clubRepository.findById(clubId).get(), - scheduleQueryService.findMonthSchedule(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), + scheduleQueryService.findMonthlySchedulesWithFilter(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), clubId, null, null))); } } diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java new file mode 100644 index 0000000..8dbdf49 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java @@ -0,0 +1,17 @@ +package com.example.moim.schedule.comment.controller; + +import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.user.dto.UserDetailsImpl; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +@Tag(name = "일정 댓글 api") +public interface ScheduleCommentControllerDocs { + @Operation(summary = "일정에 댓글 남기기") + void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); +} diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index d4f6de2..fe85ba5 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -19,42 +19,32 @@ public class ScheduleController implements ScheduleControllerDocs{ private final ScheduleQueryService scheduleQueryService; @PostMapping(value = "/schedules") - public ScheduleOutput scheduleSave(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + public ScheduleOutput createSchedule(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { return scheduleCommandService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); } @PatchMapping("/schedules/{id}") - public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + public ScheduleOutput updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { return scheduleCommandService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); } @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) - public List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleQueryService.findMonthSchedule(scheduleSearchInput); + public List getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { + return scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); } @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) - public List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleQueryService.findDaySchedule(scheduleSearchInput); + public List getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { + return scheduleQueryService.findScheduleByDay(scheduleSearchInput); } @GetMapping("/schedules/{id}") - public ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id) { + public ScheduleDetailOutput getScheduleDetail(@PathVariable Long id) { return scheduleQueryService.findScheduleDetail(id); } @DeleteMapping("/schedules/{id}") - public void scheduleDelete(@PathVariable Long id) { + public void deleteSchedule(@PathVariable Long id) { scheduleCommandService.deleteSchedule(id); } - - @PatchMapping("/schedules/close/{id}") - public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleCommandService.closeSchedule(id, userDetailsImpl.getUser()); - } - - @PostMapping("/schedules/{id}/comments") - public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleCommandService.saveComment(commentInput, userDetailsImpl.getUser()); - } } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index c995f11..2e0a7ad 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -5,7 +5,6 @@ import com.example.moim.schedule.dto.ScheduleOutput; import com.example.moim.schedule.dto.ScheduleSearchInput; import com.example.moim.schedule.dto.ScheduleUpdateInput; -import com.example.moim.schedule.dto.ScheduleVoteInput; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,25 +18,22 @@ @Tag(name = "일정 api", description = "모임(club) 안에서 category에 따라 권한 부여. creator, admin / member, newmember") public interface ScheduleControllerDocs { @Operation(summary = "일정 생성", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") - ScheduleOutput scheduleSave(@RequestBody ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + ScheduleOutput createSchedule(@RequestBody ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 수정", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") - ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + ScheduleOutput updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "한달 일정 조회", description = "쿼리파라미터 예시: /schedule?date=202404&clubId=6&search=친선 경기&category=친선 경기") - List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + List getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); @Operation(summary = "하루 일정 조회", description = "쿼리파라미터 예시: /schedule/day?date=20240910&clubId=6&search=친선 경기&category=친선 경기") - List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + List getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput); @Operation(summary = "일정 세부 조회", description = "참가면 attendance = attend, 참가 취소는 absent, 투표 안하면 notVote") - ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id); + ScheduleDetailOutput getScheduleDetail(@PathVariable Long id); @Operation(summary = "일정 삭제") - void scheduleDelete(@PathVariable Long id); - - @Operation(summary = "일정 참가 투표 마감") - void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + void deleteSchedule(@PathVariable Long id); // @Operation(summary = "일정 댓글", description = "id는 일정 id") // void scheduleComment(@RequestBody CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java index 04bd1bc..e654eb0 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java @@ -7,7 +7,4 @@ public interface ScheduleCommandService { ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user); ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user); void deleteSchedule(Long id); - void closeSchedule(Long id, User user); - void saveComment(CommentInput commentInput, User user); - } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java index 2a64299..7b78833 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -32,7 +32,6 @@ public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final ClubRepository clubRepository; private final ScheduleRepository scheduleRepository; private final UserClubRepository userClubRepository; - private final CommentRepository commentRepository; private final ApplicationEventPublisher eventPublisher; private final MatchApplicationRepository matchApplicationRepository; @@ -83,26 +82,6 @@ public void deleteSchedule(Long id) { scheduleRepository.deleteById(id); } - /** - * TODO: void -> 기본 응답, 이름 명확하게 바꾸기 closeScheduleVote 등 - * @param id - */ - @Transactional - public void closeSchedule(Long id, User user) { - Schedule schedule = scheduleRepository.findWithClubById(id); - UserClub userClub = getUserClub(schedule.getClub(), user); - if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { - throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); - } - - schedule.close(); - } - - public void saveComment(CommentInput commentInput, User user) { - Schedule schedule = getSchedule(commentInput.getId()); - commentRepository.save(Comment.createComment(user, schedule, commentInput.getContents())); - } - private Club getClub(Long clubId) { return clubRepository.findById(clubId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java index 1d8fc6e..786207a 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java @@ -7,7 +7,7 @@ import java.util.List; public interface ScheduleQueryService { - List findMonthSchedule(ScheduleSearchInput scheduleSearchInput); - List findDaySchedule(ScheduleSearchInput scheduleSearchInput); + List findMonthlySchedulesWithFilter(ScheduleSearchInput scheduleSearchInput); + List findScheduleByDay(ScheduleSearchInput scheduleSearchInput); ScheduleDetailOutput findScheduleDetail(Long scheduleId); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index f6cbcdd..c313b08 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -27,7 +27,7 @@ public class ScheduleQueryServiceImpl implements ScheduleQueryService { private final ClubRepository clubRepository; private final MatchApplicationRepository matchApplicationRepository; - public List findMonthSchedule(ScheduleSearchInput scheduleSearchInput) { + public List findMonthlySchedulesWithFilter(ScheduleSearchInput scheduleSearchInput) { Club club = getClub(scheduleSearchInput.getClubId()); return scheduleRepository.findByClubAndTime(club, @@ -38,7 +38,7 @@ public List findMonthSchedule(ScheduleSearchInput scheduleSearch .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } - public List findDaySchedule(ScheduleSearchInput scheduleSearchInput) { + public List findScheduleByDay(ScheduleSearchInput scheduleSearchInput) { LocalDateTime searchDate = LocalDateTime.of(scheduleSearchInput.getDate() / 10000, (scheduleSearchInput.getDate() / 100) % 100, scheduleSearchInput.getDate() % 100, 0, 0, 0); Club club = getClub(scheduleSearchInput.getClubId()); diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index 77d76d8..3e7f8bf 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -11,15 +11,20 @@ @RequiredArgsConstructor public class ScheduleVoteController implements ScheduleVoteControllerDocs { - private ScheduleVoteService scheduleVoteService; + private final ScheduleVoteService scheduleVoteService; @PatchMapping("/schedules/vote") - public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + public void createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { scheduleVoteService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); } @PostMapping("/schedules/encourage/{id}") - public void voteEncourage(@PathVariable Long id) { + public void encourageVote(@PathVariable Long id) { scheduleVoteService.voteEncourage(id); } + + @PatchMapping("/schedules/close/{id}") + public void closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + scheduleVoteService.closeScheduleVote(id, userDetailsImpl.getUser()); + } } diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java index 1febb58..ccf4122 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -9,8 +9,12 @@ public interface ScheduleVoteControllerDocs { @Operation(summary = "일정 참가 투표", description = "참가면 attendance = attend, 참가 취소는 absent") - void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + void createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 독려") - void voteEncourage(@PathVariable Long id); + void encourageVote(@PathVariable Long id); + + @Operation(summary = "일정 참가 투표 마감") + void closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java index 9230fb4..8d12f05 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -6,4 +6,6 @@ public interface ScheduleVoteService { void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); void voteEncourage(Long id); + void closeScheduleVote(Long id, User user); + } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index d849c20..c287c52 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -1,7 +1,9 @@ package com.example.moim.schedule.vote.service; +import com.example.moim.club.entity.Club; import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.schedule.dto.ScheduleVoteInput; @@ -68,7 +70,26 @@ public void voteEncourage(Long id) { eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); } + /** + * TODO: void -> 기본 응답, 이름 명확하게 바꾸기 closeScheduleVote 등 + * @param id + */ + @Transactional + public void closeScheduleVote(Long id, User user) { + Schedule schedule = scheduleRepository.findWithClubById(id); + UserClub userClub = getUserClub(schedule.getClub(), user); + if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { + throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); + } + + schedule.close(); + } + private Schedule getSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); } + + private UserClub getUserClub(Club club, User user) { + return userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + } } diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java index c3ae122..994a4be 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java @@ -6,15 +6,11 @@ import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.match.repository.MatchApplicationRepository; -import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; import com.example.moim.schedule.dto.*; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; import com.example.moim.user.dto.SignupInput; import com.example.moim.user.entity.User; import org.junit.jupiter.api.BeforeEach; @@ -28,7 +24,6 @@ import org.springframework.mock.web.MockMultipartFile; import java.time.LocalDateTime; -import java.util.List; import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -169,41 +164,4 @@ void deleteSchedule() { //then verify(scheduleRepository, times(1)).deleteById(any(Long.class)); } - - @Test - @DisplayName("운영진은 일정 투표를 마감할 수 있다") - void closeSchedule() { - //given - Club club = Club.from(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.from(club, scheduleInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - //when - when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); - when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - scheduleCommandService.closeSchedule(1L, user); - //then - verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); - verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); - } - - @Test - @DisplayName("일반 회원이 일정 투표를 마감할 때 예외가 발생한다") - void closeSchedule_wrong_permission() { - //given - Club club = Club.from(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.from(club, scheduleInput); - UserClub userClub = UserClub.createUserClub(user, club); - //when - when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); - when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - //then - Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleCommandService.closeSchedule(1L, user); - }); - assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); - verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); - verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); - } } \ No newline at end of file diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java index 698571a..5d35d25 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -70,7 +70,7 @@ void findSchedule() { //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleQueryService.findMonthSchedule(scheduleSearchInput); + List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); //then assertThat(result.size()).isEqualTo(1); @@ -91,7 +91,7 @@ void findSchedule_zero_schedule() { //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of()); - List result = scheduleQueryService.findMonthSchedule(scheduleSearchInput); + List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); //then assertThat(result.size()).isEqualTo(0); @@ -110,7 +110,7 @@ void findDaySchedule() { //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleQueryService.findDaySchedule(scheduleSearchInput); + List result = scheduleQueryService.findScheduleByDay(scheduleSearchInput); //then assertThat(result.size()).isEqualTo(1); diff --git a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java index db4f29d..52aef75 100644 --- a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -6,11 +6,13 @@ import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; +import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.schedule.dto.ScheduleInput; import com.example.moim.schedule.dto.ScheduleVoteInput; import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; import com.example.moim.schedule.vote.entity.ScheduleVote; import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; @@ -31,6 +33,7 @@ import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.mockito.Mockito.times; @@ -38,8 +41,6 @@ @ExtendWith(MockitoExtension.class) class ScheduleVoteServiceImplTest { - @Mock - private ClubRepository clubRepository; @Mock private ScheduleRepository scheduleRepository; @Mock @@ -47,8 +48,6 @@ class ScheduleVoteServiceImplTest { @Mock private ScheduleVoteRepository scheduleVoteRepository; @Mock - private MatchApplicationRepository matchApplicationRepository; - @Mock private ApplicationEventPublisher applicationEventPublisher; @InjectMocks private ScheduleVoteServiceImpl scheduleVoteService; @@ -136,4 +135,42 @@ void voteEncourage() { } + @Test + @DisplayName("운영진은 일정 투표를 마감할 수 있다") + void closeSchedule() { + //given + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + Schedule schedule = Schedule.from(club, scheduleInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + //when + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + scheduleVoteService.closeScheduleVote(1L, user); + //then + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + } + + @Test + @DisplayName("일반 회원이 일정 투표를 마감할 때 예외가 발생한다") + void closeSchedule_wrong_permission() { + //given + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + Schedule schedule = Schedule.from(club, scheduleInput); + UserClub userClub = UserClub.createUserClub(user, club); + //when + when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + //then + Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { + scheduleVoteService.closeScheduleVote(1L, user); + }); + assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); + verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + } + + } \ No newline at end of file From f50dfb70301813b9186f5e33cf27e396a0f7d10d Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 18 Apr 2025 11:48:46 +0900 Subject: [PATCH 13/33] =?UTF-8?q?feat(#33):=20=EB=8C=93=EA=B8=80=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EA=B8=B0=EB=8A=A5,=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=ED=98=84=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 8 +- .../moim/config/security/SecurityConfig.java | 6 +- .../moim/global/exception/ResponseCode.java | 2 +- .../controller/ScheduleCommentController.java | 24 +++- .../ScheduleCommentControllerDocs.java | 4 +- .../schedule/comment/dto/CommentInput.java | 18 +++ .../{ => comment}/dto/CommentOutput.java | 4 +- .../ScheduleCommentCommandService.java | 5 +- .../ScheduleCommentCommandServiceImpl.java | 22 +++- .../moim/schedule/dto/CommentInput.java | 11 -- .../service/ScheduleQueryServiceImpl.java | 5 + .../controller/ScheduleVoteController.java | 2 +- .../ScheduleVoteControllerDocs.java | 2 +- .../{ => vote}/dto/ScheduleVoteInput.java | 2 +- .../vote/service/ScheduleVoteService.java | 2 +- .../vote/service/ScheduleVoteServiceImpl.java | 2 +- ...ScheduleCommentCommandServiceImplTest.java | 119 ++++++++++++++++++ .../service/ScheduleVoteServiceImplTest.java | 4 +- 18 files changed, 205 insertions(+), 37 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/comment/dto/CommentInput.java rename src/main/java/com/example/moim/schedule/{ => comment}/dto/CommentOutput.java (72%) delete mode 100644 src/main/java/com/example/moim/schedule/dto/CommentInput.java rename src/main/java/com/example/moim/schedule/{ => vote}/dto/ScheduleVoteInput.java (86%) create mode 100644 src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java diff --git a/deploy/local.init.sql b/deploy/local.init.sql index 8ac668a..72f735a 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -27,4 +27,10 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 1, 1); INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) -VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); \ No newline at end of file +VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); + +-- 스케줄 생성(3번 동아리) +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file diff --git a/src/main/java/com/example/moim/config/security/SecurityConfig.java b/src/main/java/com/example/moim/config/security/SecurityConfig.java index 3705755..cceafa3 100644 --- a/src/main/java/com/example/moim/config/security/SecurityConfig.java +++ b/src/main/java/com/example/moim/config/security/SecurityConfig.java @@ -69,14 +69,14 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { * FIXME: 이건 API 수동 테스트할때만 필요한 것 */ httpSecurity.csrf(csrf -> csrf - .ignoringRequestMatchers("/club/**") + .ignoringRequestMatchers("/clubs/**", "/schedules/**", "/notices/**") ); //From 로그인 방식 disable httpSecurity.formLogin(AbstractHttpConfigurer::disable); //http basic 인증 방식 disable httpSecurity.httpBasic(AbstractHttpConfigurer::disable); /** - * FIXME: 이건 API 수동 테스트할때만 필요한 것 + * FIXME: 이건 API 수동 테스트할때만 필요한 것 : 주석 제거하기 */ //경로별 인가 작업 // httpSecurity.authorizeHttpRequests((auth) -> auth @@ -90,7 +90,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { httpSecurity.authorizeHttpRequests((auth) -> auth.anyRequest().permitAll()); /** - * FIXME: 이건 API 수동 테스트할때만 필요한 것 + * FIXME: 이건 API 수동 테스트할때만 필요한 것 : 주석 제거해야 함 */ //JWTFilter 등록 // httpSecurity diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 359b247..358f998 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -13,7 +13,7 @@ public enum ResponseCode { // 정상 code - OK(HttpStatus.OK,"2000", "Ok"), + OK(HttpStatus.OK,"2000", "OK"), // Common Error _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON000", "서버 에러, 관리자에게 문의 바랍니다."), diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java index 555ebb8..605628b 100644 --- a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java @@ -1,11 +1,17 @@ package com.example.moim.schedule.comment.controller; +import com.example.moim.global.exception.BaseResponse; +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.schedule.comment.service.ScheduleCommentCommandService; -import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.schedule.comment.dto.CommentInput; import com.example.moim.user.dto.UserDetailsImpl; +import com.example.moim.user.entity.User; +import com.example.moim.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -16,8 +22,20 @@ public class ScheduleCommentController { private final ScheduleCommentCommandService scheduleCommentCommandService; + /** + * FIXME: 컨트롤러 수동테스트를 위해 필요한 코드, 추후에 지울 것. + */ + private final UserRepository userRepository; + @PostMapping("/schedules/{id}/comments") - public void scheduleComment(@RequestBody @Valid CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleCommentCommandService.saveComment(commentInput, userDetailsImpl.getUser()); + public BaseResponse scheduleComment(@RequestBody @Valid CommentInput commentInput, @PathVariable("id") Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + /** + * FIXME: 컨트롤러 수동테스트를 위해 필요한 코드, 추후에 지울 것. + */ +// public BaseResponse scheduleComment(@RequestBody @Valid CommentInput commentInput, @PathVariable("id") Long scheduleId) { +// User user = userRepository.findById(3L).get(); + CommentOutput commentOutput = scheduleCommentCommandService.saveComment(commentInput, scheduleId, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(commentOutput, ResponseCode.OK); } + } diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java index 8dbdf49..65fb2c9 100644 --- a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.comment.controller; -import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.schedule.comment.dto.CommentInput; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; @@ -8,8 +8,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.RequestBody; -import java.util.List; - @Tag(name = "일정 댓글 api") public interface ScheduleCommentControllerDocs { @Operation(summary = "일정에 댓글 남기기") diff --git a/src/main/java/com/example/moim/schedule/comment/dto/CommentInput.java b/src/main/java/com/example/moim/schedule/comment/dto/CommentInput.java new file mode 100644 index 0000000..83fb86e --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/dto/CommentInput.java @@ -0,0 +1,18 @@ +package com.example.moim.schedule.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CommentInput { + @NotBlank(message = "댓글을 입력해주세요.") + private String contents; + + @Builder + public CommentInput(String contents) { + this.contents = contents; + } +} diff --git a/src/main/java/com/example/moim/schedule/dto/CommentOutput.java b/src/main/java/com/example/moim/schedule/comment/dto/CommentOutput.java similarity index 72% rename from src/main/java/com/example/moim/schedule/dto/CommentOutput.java rename to src/main/java/com/example/moim/schedule/comment/dto/CommentOutput.java index 676ef0b..cbd4f1b 100644 --- a/src/main/java/com/example/moim/schedule/dto/CommentOutput.java +++ b/src/main/java/com/example/moim/schedule/comment/dto/CommentOutput.java @@ -1,4 +1,4 @@ -package com.example.moim.schedule.dto; +package com.example.moim.schedule.comment.dto; import com.example.moim.schedule.comment.entity.Comment; import lombok.Data; @@ -8,10 +8,12 @@ public class CommentOutput { private Long id; private String userName; private String contents; + private String createdDate; public CommentOutput(Comment comment) { this.id = comment.getId(); this.userName = comment.getUser().getName(); this.contents = comment.getContents(); + this.createdDate = comment.getCreatedDate().toString(); } } diff --git a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java index ba8a647..8a470dd 100644 --- a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java @@ -1,8 +1,9 @@ package com.example.moim.schedule.comment.service; -import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.schedule.comment.dto.CommentInput; +import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.user.entity.User; public interface ScheduleCommentCommandService { - void saveComment(CommentInput commentInput, User user); + CommentOutput saveComment(CommentInput commentInput, Long scheduleId, User user); } diff --git a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java index 4697b03..08336a5 100644 --- a/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java @@ -1,9 +1,13 @@ package com.example.moim.schedule.comment.service; +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.UserClub; +import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.comment.repository.CommentRepository; -import com.example.moim.schedule.dto.CommentInput; +import com.example.moim.schedule.comment.dto.CommentInput; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; @@ -17,14 +21,24 @@ public class ScheduleCommentCommandServiceImpl implements ScheduleCommentCommand private final CommentRepository commentRepository; private final ScheduleRepository scheduleRepository; + private final UserClubRepository userClubRepository; - public void saveComment(CommentInput commentInput, User user) { - Schedule schedule = getSchedule(commentInput.getId()); - commentRepository.save(Comment.createComment(user, schedule, commentInput.getContents())); + public CommentOutput saveComment(CommentInput commentInput, Long scheduleId, User user) { + Schedule schedule = getSchedule(scheduleId); + + getUserClub(schedule.getClub(), user); // 가입된 회원인지 확인하기 위해 필요한 것 + + Comment savedComment = commentRepository.save(Comment.createComment(user, schedule, commentInput.getContents())); + + return new CommentOutput(savedComment); } private Schedule getSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); } + private UserClub getUserClub(Club club, User user) { + return userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + } + } diff --git a/src/main/java/com/example/moim/schedule/dto/CommentInput.java b/src/main/java/com/example/moim/schedule/dto/CommentInput.java deleted file mode 100644 index 993d6f2..0000000 --- a/src/main/java/com/example/moim/schedule/dto/CommentInput.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.example.moim.schedule.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.Data; - -@Data -public class CommentInput { - private Long id; - @NotBlank(message = "댓글을 입력해주세요.") - private String contents; -} diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index c313b08..d158ced 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -48,6 +48,11 @@ public List findScheduleByDay(ScheduleSearchInput scheduleSearch .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } + /** + * FIXME: 세부 일정 페이지 보고 데이터 넘겨주기. 댓글도 포함되어야 함 + * @param scheduleId + * @return + */ public ScheduleDetailOutput findScheduleDetail(Long scheduleId) { Schedule schedule = getSchedule(scheduleId); diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index 3e7f8bf..dcb8c91 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.vote.controller; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.schedule.vote.service.ScheduleVoteService; import com.example.moim.user.dto.UserDetailsImpl; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java index ccf4122..2a824f8 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.vote.controller; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; import org.springframework.security.core.annotation.AuthenticationPrincipal; diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleVoteInput.java b/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java similarity index 86% rename from src/main/java/com/example/moim/schedule/dto/ScheduleVoteInput.java rename to src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java index f7fe139..d55fcb2 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleVoteInput.java +++ b/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java @@ -1,4 +1,4 @@ -package com.example.moim.schedule.dto; +package com.example.moim.schedule.vote.dto; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java index 8d12f05..e53727a 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -1,6 +1,6 @@ package com.example.moim.schedule.vote.service; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.user.entity.User; public interface ScheduleVoteService { diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index c287c52..eea8b21 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -6,7 +6,7 @@ import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.notification.dto.ScheduleEncourageEvent; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; diff --git a/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java new file mode 100644 index 0000000..d2f8c32 --- /dev/null +++ b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java @@ -0,0 +1,119 @@ +package com.example.moim.schedule.comment.service; + +import com.example.moim.club.dto.request.ClubInput; +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.UserClub; +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.*; +import com.example.moim.schedule.comment.entity.Comment; +import com.example.moim.schedule.comment.repository.CommentRepository; +import com.example.moim.schedule.comment.dto.CommentInput; +import com.example.moim.schedule.dto.ScheduleInput; +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; +import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.user.dto.SignupInput; +import com.example.moim.user.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ScheduleCommentCommandServiceImplTest { + + @Mock + private CommentRepository commentRepository; + @Mock + private ScheduleRepository scheduleRepository; + @Mock + private UserClubRepository userClubRepository; + + @InjectMocks + private ScheduleCommentCommandServiceImpl scheduleCommentCommandService; + + private ScheduleInput scheduleInput; + private SignupInput signupInput; + private ClubInput clubInput; + private CommentInput commentInput; + + @BeforeEach + void init() { + // club, userClub, Schedule 생성 + this.scheduleInput = ScheduleInput.builder().clubId(1L).title("title").location("location") + .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) + .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) + .minPeople(10) + .category("정기 운동") + .note("note").build(); + this.signupInput = SignupInput.builder().email("email").password("password") + .name("name").birthday("birthday").gender(Gender.WOMAN.getKoreanName()).build(); + this.clubInput = ClubInput.builder().title("title").explanation("explanation").introduction("introduction") + .clubCategory(ClubCategory.SMALL_GROUP.getKoreanName()).university("university").gender(Gender.UNISEX.getKoreanName()) + .activityArea(ActivityArea.SEOUL.getKoreanName()).ageRange(AgeRange.TWENTIES.getKoreanName()).sportsType(SportsType.SOCCER.getKoreanName()) + .clubPassword("clubPassword").profileImg(new MockMultipartFile("file", "file".getBytes())) + .mainUniformColor("mainUniformColor").subUniformColor("subUniformColor").build(); + + // commentInput 생성 + this.commentInput = CommentInput.builder().contents("일정이 있어 참가 못합니다").build(); + } + + @Test + @DisplayName("회원은 일정에 댓글을 달 수 있다.") + void saveComment() { + // given + Club club = Club.from(clubInput, "/image"); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + Schedule schedule = Schedule.from(club, scheduleInput); + Comment comment = Comment.createComment(user, schedule, commentInput.getContents()); + LocalDateTime createdDate = LocalDateTime.now(); + ReflectionTestUtils.setField(comment, "createdDate", createdDate); + + // when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.of(userClub)); + when(commentRepository.save(any(Comment.class))).thenReturn(comment); + scheduleCommentCommandService.saveComment(commentInput, 1L, user); + + // then + verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + verify(commentRepository, times(1)).save(any(Comment.class)); + } + + @Test + @DisplayName("비회원은 일정에 댓글을 달 수 없다.") + void saveComment_non_member() { + // given + Club club = Club.from(clubInput, "/image"); + User user = User.createUser(signupInput); + Schedule schedule = Schedule.from(club, scheduleInput); + Comment comment = Comment.createComment(user, schedule, commentInput.getContents()); + + // when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.empty()); + + // then + assertThrows(ScheduleControllerAdvice.class, () -> { + scheduleCommentCommandService.saveComment(commentInput, 1L, user); + }); + verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + verify(commentRepository, times(0)).save(any(Comment.class)); + } +} \ No newline at end of file diff --git a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java index 52aef75..874011b 100644 --- a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -3,14 +3,12 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.entity.Club; import com.example.moim.club.entity.UserClub; -import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.schedule.dto.ScheduleInput; -import com.example.moim.schedule.dto.ScheduleVoteInput; +import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; From effe39178361bc9a0a4a743a8c9fa6c5b5228c2c Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 18 Apr 2025 16:53:18 +0900 Subject: [PATCH 14/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/moim/external/fcm/FcmConfig.java | 82 +++++++++---------- .../controller/ScheduleController.java | 39 ++++++--- .../controller/ScheduleControllerDocs.java | 16 ++-- .../service/ScheduleCommandService.java | 2 +- .../service/ScheduleCommandServiceImpl.java | 36 +++++--- .../service/NotificationServiceTest.java | 2 +- .../ScheduleCommandServiceImplTest.java | 15 +++- 7 files changed, 115 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/example/moim/external/fcm/FcmConfig.java b/src/main/java/com/example/moim/external/fcm/FcmConfig.java index 77a68be..b566063 100644 --- a/src/main/java/com/example/moim/external/fcm/FcmConfig.java +++ b/src/main/java/com/example/moim/external/fcm/FcmConfig.java @@ -1,41 +1,41 @@ -package com.example.moim.external.fcm; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; -import com.google.firebase.messaging.FirebaseMessaging; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; - -@Configuration -public class FcmConfig { - - @Bean - FirebaseMessaging firebaseMessaging() throws IOException { - ClassPathResource resource = new ClassPathResource("secret/sample-firebase-test-f3c27-firebase-adminsdk-fbsvc-c15b4b66c6.json"); - InputStream refreshToken = resource.getInputStream(); - - FirebaseApp firebaseApp = null; - List firebaseAppList = FirebaseApp.getApps(); - - if (firebaseAppList != null && !firebaseAppList.isEmpty()) { - for (FirebaseApp app : firebaseAppList) { - if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { - firebaseApp = app; - } - } - } else { - FirebaseOptions options = FirebaseOptions.builder() - .setCredentials(GoogleCredentials.fromStream(refreshToken)) - .build(); - - firebaseApp = FirebaseApp.initializeApp(options); - } - - return FirebaseMessaging.getInstance(firebaseApp); - } -} +//package com.example.moim.external.fcm; +// +//import com.google.auth.oauth2.GoogleCredentials; +//import com.google.firebase.FirebaseApp; +//import com.google.firebase.FirebaseOptions; +//import com.google.firebase.messaging.FirebaseMessaging; +//import java.io.IOException; +//import java.io.InputStream; +//import java.util.List; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.core.io.ClassPathResource; +// +//@Configuration +//public class FcmConfig { +// +// @Bean +// FirebaseMessaging firebaseMessaging() throws IOException { +// ClassPathResource resource = new ClassPathResource("secret/sample-firebase-test-f3c27-firebase-adminsdk-fbsvc-c15b4b66c6.json"); +// InputStream refreshToken = resource.getInputStream(); +// +// FirebaseApp firebaseApp = null; +// List firebaseAppList = FirebaseApp.getApps(); +// +// if (firebaseAppList != null && !firebaseAppList.isEmpty()) { +// for (FirebaseApp app : firebaseAppList) { +// if (app.getName().equals(FirebaseApp.DEFAULT_APP_NAME)) { +// firebaseApp = app; +// } +// } +// } else { +// FirebaseOptions options = FirebaseOptions.builder() +// .setCredentials(GoogleCredentials.fromStream(refreshToken)) +// .build(); +// +// firebaseApp = FirebaseApp.initializeApp(options); +// } +// +// return FirebaseMessaging.getInstance(firebaseApp); +// } +//} diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index fe85ba5..8ef991b 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -1,9 +1,13 @@ package com.example.moim.schedule.controller; +import com.example.moim.global.exception.BaseResponse; +import com.example.moim.global.exception.ResponseCode; import com.example.moim.schedule.dto.*; import com.example.moim.schedule.service.ScheduleCommandService; import com.example.moim.schedule.service.ScheduleQueryService; import com.example.moim.user.dto.UserDetailsImpl; +import com.example.moim.user.entity.User; +import com.example.moim.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -14,37 +18,46 @@ @RestController @RequiredArgsConstructor -public class ScheduleController implements ScheduleControllerDocs{ +public class ScheduleController implements ScheduleControllerDocs { private final ScheduleCommandService scheduleCommandService; private final ScheduleQueryService scheduleQueryService; + private final UserRepository userRepository; + @PostMapping(value = "/schedules") - public ScheduleOutput createSchedule(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleCommandService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); + public BaseResponse createSchedule(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + ScheduleOutput scheduleOutput = scheduleCommandService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleOutput, ResponseCode.OK); } @PatchMapping("/schedules/{id}") - public ScheduleOutput updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleCommandService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); + public BaseResponse updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + ScheduleOutput scheduleOutput = scheduleCommandService.updateSchedule(scheduleUpdateInput, id, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleOutput, ResponseCode.OK); } @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) - public List getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); + public BaseResponse> getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { + List scheduleList = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); + return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) - public List getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleQueryService.findScheduleByDay(scheduleSearchInput); + public BaseResponse> getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { + List scheduleList = scheduleQueryService.findScheduleByDay(scheduleSearchInput); + return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } @GetMapping("/schedules/{id}") - public ScheduleDetailOutput getScheduleDetail(@PathVariable Long id) { - return scheduleQueryService.findScheduleDetail(id); + public BaseResponse getScheduleDetail(@PathVariable Long id) { + ScheduleDetailOutput scheduleDetail = scheduleQueryService.findScheduleDetail(id); + return BaseResponse.onSuccess(scheduleDetail, ResponseCode.OK); } @DeleteMapping("/schedules/{id}") - public void deleteSchedule(@PathVariable Long id) { - scheduleCommandService.deleteSchedule(id); + public BaseResponse deleteSchedule(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { +// public BaseResponse deleteSchedule(@PathVariable Long id) { + String result = scheduleCommandService.deleteSchedule(id, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(result, ResponseCode.OK); } } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index 2e0a7ad..219b3b9 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -1,5 +1,6 @@ package com.example.moim.schedule.controller; +import com.example.moim.global.exception.BaseResponse; import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleInput; import com.example.moim.schedule.dto.ScheduleOutput; @@ -18,23 +19,22 @@ @Tag(name = "일정 api", description = "모임(club) 안에서 category에 따라 권한 부여. creator, admin / member, newmember") public interface ScheduleControllerDocs { @Operation(summary = "일정 생성", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") - ScheduleOutput createSchedule(@RequestBody ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + BaseResponse createSchedule(@RequestBody ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 수정", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") - ScheduleOutput updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + BaseResponse updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "한달 일정 조회", description = "쿼리파라미터 예시: /schedule?date=202404&clubId=6&search=친선 경기&category=친선 경기") - List getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + BaseResponse> getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); @Operation(summary = "하루 일정 조회", description = "쿼리파라미터 예시: /schedule/day?date=20240910&clubId=6&search=친선 경기&category=친선 경기") - List getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + BaseResponse> getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput); @Operation(summary = "일정 세부 조회", description = "참가면 attendance = attend, 참가 취소는 absent, 투표 안하면 notVote") - ScheduleDetailOutput getScheduleDetail(@PathVariable Long id); + BaseResponse getScheduleDetail(@PathVariable Long id); @Operation(summary = "일정 삭제") - void deleteSchedule(@PathVariable Long id); + BaseResponse deleteSchedule(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); +// BaseResponse deleteSchedule(@PathVariable Long id); -// @Operation(summary = "일정 댓글", description = "id는 일정 id") -// void scheduleComment(@RequestBody CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java index e654eb0..981b64d 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java @@ -6,5 +6,5 @@ public interface ScheduleCommandService { ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user); ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user); - void deleteSchedule(Long id); + String deleteSchedule(Long id, User user); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java index 7b78833..6b70d49 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -36,11 +36,12 @@ public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final MatchApplicationRepository matchApplicationRepository; public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { + /** + * TODO: 친선 매치일 경우 상대방 팀에도 일정 생성되도록 처리해야 하나? + */ Club club = getClub(scheduleInput.getClubId()); - UserClub userClub = getUserClub(club, user); - if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { - throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); - } + + validateClubStaff(getUserClub(club, user)); Schedule schedule = scheduleRepository.save(Schedule.from(club, scheduleInput)); @@ -54,12 +55,12 @@ public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { @Transactional public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user) { + /** + * TODO: 친선 매치일 경우 상대방 팀에도 일정 수정 반영해야 하나? + */ Club club = getClub(scheduleUpdateInput.getClubId()); - UserClub userClub = getUserClub(club, user); - if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { - throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); - } + validateClubStaff(getUserClub(club, user)); Schedule schedule = getSchedule(id); @@ -74,12 +75,19 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo } /** - * TODO: void -> 기본 응답 - * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) + * TODO: 친선 매치라면 같이 삭제해주는 로직 필요 * @param id */ - public void deleteSchedule(Long id) { + public String deleteSchedule(Long id, User user) { + + Schedule schedule = getSchedule(id); + + // 운영진인지 권한 확인 + validateClubStaff(getUserClub(schedule.getClub(), user)); + scheduleRepository.deleteById(id); + + return "스케줄을 정상적으로 취소하였습니다."; } private Club getClub(Long clubId) { @@ -93,4 +101,10 @@ private UserClub getUserClub(Club club, User user) { private Schedule getSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); } + + private void validateClubStaff(UserClub userClub) { + if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { + throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); + } + } } diff --git a/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java b/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java index 16a694a..62c269f 100644 --- a/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java +++ b/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java @@ -152,7 +152,7 @@ void shouldSaveAndSendNotifications() { .build() ); - Club joinedClub = Club.createClub( + Club joinedClub = Club.from( ClubInput.builder() .title("Club Title") .clubCategory("동아리") diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java index 994a4be..4d4e4ac 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java @@ -155,13 +155,24 @@ void updateSchedule_wrong_permission() { } @Test - @DisplayName("일정을 삭제할 수 있다") + @DisplayName("운영진은 일정을 삭제할 수 있다") void deleteSchedule() { //given Long id = 1L; + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + //when - scheduleCommandService.deleteSchedule(id); + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + String result = scheduleCommandService.deleteSchedule(id, user); + //then + assertThat(result).isEqualTo("스케줄을 정상적으로 취소하였습니다."); + verify(scheduleRepository, times(1)).deleteById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); verify(scheduleRepository, times(1)).deleteById(any(Long.class)); } } \ No newline at end of file From 416e98f7237c2c45fe9d47595a42e7530be97507 Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 18 Apr 2025 17:57:39 +0900 Subject: [PATCH 15/33] =?UTF-8?q?refacotr(#33):=20=ED=95=9C=EB=8B=AC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20=EC=99=84=EB=A3=8C=20-?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EA=B0=80=EB=8F=85=EC=84=B1=EC=9D=84=20?= =?UTF-8?q?=EB=86=92=EC=9D=B4=EA=B8=B0=20=EC=9C=84=ED=95=B4=20=EB=82=A0?= =?UTF-8?q?=EC=A7=9C=EB=A5=BC=20year,=20month=20=EB=B3=80=EC=88=98?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EB=A6=AC=20-=20ScheduleCategory=20?= =?UTF-8?q?=EA=B0=80=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EC=99=80=EB=8F=84=20=EA=B0=92=EC=9D=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 10 ++++-- .../controller/ScheduleController.java | 2 +- .../controller/ScheduleControllerDocs.java | 4 +-- .../schedule/dto/ScheduleSearchInput.java | 5 +++ .../repository/ScheduleRepositoryImpl.java | 18 ++++------ .../service/ScheduleQueryServiceImpl.java | 28 +++++++++++++-- .../ScheduleRepositoryImplTest.java | 34 +++++++++++++++++-- 7 files changed, 80 insertions(+), 21 deletions(-) diff --git a/deploy/local.init.sql b/deploy/local.init.sql index 72f735a..b67e10b 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -31,6 +31,12 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성(3번 동아리) insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file +values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-30 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-28 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 8ef991b..9aca775 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -37,7 +37,7 @@ public BaseResponse updateSchedule(@RequestBody ScheduleUpdateIn } @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) - public BaseResponse> getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { + public BaseResponse> searchScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { List scheduleList = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index 219b3b9..2111aca 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -24,8 +24,8 @@ public interface ScheduleControllerDocs { @Operation(summary = "일정 수정", description = "startTime, endTime 형식은 yyyy-MM-dd HH:mm") BaseResponse updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); - @Operation(summary = "한달 일정 조회", description = "쿼리파라미터 예시: /schedule?date=202404&clubId=6&search=친선 경기&category=친선 경기") - BaseResponse> getScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + @Operation(summary = "한달 일정 조회", description = "카테고리는 친선 매치/정기 운동/대회/기타 중에 하나여야 합니다(띄어쓰기까지 포함)") + BaseResponse> searchScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); @Operation(summary = "하루 일정 조회", description = "쿼리파라미터 예시: /schedule/day?date=20240910&clubId=6&search=친선 경기&category=친선 경기") BaseResponse> getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput); diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java index 91e4d9c..de5085e 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java @@ -1,10 +1,15 @@ package com.example.moim.schedule.dto; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Pattern; import lombok.Builder; import lombok.Data; @Data public class ScheduleSearchInput { + + @Schema(description = "조회 기준 년월 (YYYYMM 형식, 예: 202404)", example = "202404") + @Pattern(regexp = "^[0-9]{6}$", message = "날짜는 YYYYMM 형식이어야 합니다.") private final Integer date; private final Long clubId; private final String search; diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java index 85885dc..bda58e9 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -9,6 +9,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; import static com.example.moim.schedule.entity.QSchedule.schedule; import static org.springframework.util.StringUtils.hasText; @@ -23,21 +24,13 @@ public ScheduleRepositoryImpl(EntityManager em) { this.queryFactory = new JPAQueryFactory(em); } - /** - * TODO: 매개변수 중에 search, category 는 사용을 안함. 추후에 지울 것 - * @param club - * @param startTime - * @param endTime - * @param search - * @param category - * @return - */ @Override public List findByClubAndTime(Club club, LocalDateTime startTime, LocalDateTime endTime, String search, String category) { return queryFactory .selectFrom(schedule) .orderBy(schedule.startTime.asc()) - .where(schedule.club.eq(club), schedule.startTime.goe(startTime), schedule.endTime.loe(endTime)) + .where(schedule.club.eq(club), schedule.startTime.goe(startTime), schedule.endTime.loe(endTime), + searchContains(search), categoryEq(category)) .fetch(); } @@ -49,8 +42,9 @@ private BooleanExpression searchContains(String search) { } private BooleanExpression categoryEq(String category) { - if (hasText(category)) { - return schedule.category.eq(ScheduleCategory.valueOf(category)); + Optional scheduleCategory = ScheduleCategory.fromKoreanName(category); + if (hasText(category) && scheduleCategory.isPresent()) { + return schedule.category.eq(scheduleCategory.get()); } return null; } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index d158ced..400bc09 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -1,7 +1,9 @@ package com.example.moim.schedule.service; import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.ClubRepository; +import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.dto.MatchApplyClubOutput; import com.example.moim.match.repository.MatchApplicationRepository; @@ -11,7 +13,9 @@ import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.user.entity.User; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -19,20 +23,36 @@ import java.util.List; import java.util.stream.Collectors; +@Slf4j @Service @RequiredArgsConstructor public class ScheduleQueryServiceImpl implements ScheduleQueryService { private final ScheduleRepository scheduleRepository; private final ClubRepository clubRepository; + private final UserClubRepository userClubRepository; private final MatchApplicationRepository matchApplicationRepository; + /** + * TODO: 기획에 맞게, 스케줄 조회 기준 바꾸기 + * User 가 가진 스케줄로 조회해야하는거 아닌가..? + * 그럼 이거 구조 아예 다시 바꿔야 할 것 같은데... + * @param scheduleSearchInput + * @return + */ public List findMonthlySchedulesWithFilter(ScheduleSearchInput scheduleSearchInput) { Club club = getClub(scheduleSearchInput.getClubId()); + int year = scheduleSearchInput.getDate() / 100; + log.info("year : {}", year); + int month = scheduleSearchInput.getDate() % 100; + log.info("month : {}", month); + + LocalDateTime startDate = LocalDateTime.of(year, month, 1, 0, 0, 0).minusDays(6); + LocalDateTime endDate = LocalDateTime.of(year, month, Month.of(month).minLength(), 23, 59, 59).plusDays(6); + return scheduleRepository.findByClubAndTime(club, - LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, 1, 0, 0, 0).minusDays(6), - LocalDateTime.of(scheduleSearchInput.getDate() / 100, scheduleSearchInput.getDate() % 100, Month.of(scheduleSearchInput.getDate() % 100).minLength(), 23, 59, 59).plusDays(6), + startDate, endDate, scheduleSearchInput.getSearch(), scheduleSearchInput.getCategory()) .stream().map(ScheduleOutput::new).collect(Collectors.toList()); @@ -68,4 +88,8 @@ private Club getClub(Long clubId) { private Schedule getSchedule(Long scheduleId) { return scheduleRepository.findById(scheduleId).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.SCHEDULE_NOT_FOUND)); } + + private UserClub getUserClub(Club club, User user) { + return userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + } } diff --git a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java index 5b3172e..5abd057 100644 --- a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java +++ b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java @@ -32,9 +32,9 @@ class ScheduleRepositoryImplTest { void findByClubAndTime() { //given Club club = clubRepository.findById(3L).get(); - LocalDateTime startTime = LocalDateTime.of(2024,3,10,9, 0,0); + LocalDateTime startTime = LocalDateTime.of(2024,3,1,0, 0,0); LocalDateTime endTime = LocalDateTime.now(); - String search = "title"; + String search = null; String category = "category"; //when List result = scheduleRepository.findByClubAndTime(club, startTime, endTime, search, category); @@ -44,6 +44,36 @@ void findByClubAndTime() { assertThat(result.get(1).getId()).isEqualTo(2L); } + @Test + @DisplayName("동아리 별로 스케줄을 조회할 때, 잘못된 title 이 검색어로 포함되면 검색 결과가 나오지 않는다.") + void findByClubAndTime_wrong_title() { + //given + Club club = clubRepository.findById(3L).get(); + LocalDateTime startTime = LocalDateTime.of(2024,3,1,0, 0,0); + LocalDateTime endTime = LocalDateTime.now(); + String search = "alkdsjglksjglks"; + String category = "category"; + //when + List result = scheduleRepository.findByClubAndTime(club, startTime, endTime, search, category); + //then + assertThat(result.size()).isEqualTo(0); + } + + @Test + @DisplayName("동아리 별로 스케줄을 조회할 때, 잘못된 category 를 입력해도 결과는 나온다.") + void findByClubAndTime_wrong_category() { + //given + Club club = clubRepository.findById(3L).get(); + LocalDateTime startTime = LocalDateTime.of(2024,3,1,0, 0,0); + LocalDateTime endTime = LocalDateTime.now(); + String search = null; + String category = "wrong"; + //when + List result = scheduleRepository.findByClubAndTime(club, startTime, endTime, search, category); + //then + assertThat(result.size()).isEqualTo(2); + } + @Test void findScheduleById() { //given From ad5b836e627d581f08f62a4f4fb2d81aeb1f59fc Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 18 Apr 2025 22:34:50 +0900 Subject: [PATCH 16/33] =?UTF-8?q?refactor(#33):=20=ED=95=98=EB=A3=A8=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 8 ++-- .../moim/main/controller/MainController.java | 2 +- .../moim/main/service/MainService.java | 10 +++-- .../controller/ScheduleController.java | 13 +++--- .../controller/ScheduleControllerDocs.java | 8 ++-- .../schedule/dto/ScheduleSearchDayInput.java | 21 ++++++++++ ...put.java => ScheduleSearchMonthInput.java} | 4 +- .../service/ScheduleQueryService.java | 9 ++-- .../service/ScheduleQueryServiceImpl.java | 41 +++++++++++-------- .../service/ScheduleQueryServiceImplTest.java | 36 ++++++++++++---- 10 files changed, 102 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/dto/ScheduleSearchDayInput.java rename src/main/java/com/example/moim/schedule/dto/{ScheduleSearchInput.java => ScheduleSearchMonthInput.java} (82%) diff --git a/deploy/local.init.sql b/deploy/local.init.sql index b67e10b..32ff194 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -33,10 +33,10 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-30 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-28 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file +values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file diff --git a/src/main/java/com/example/moim/main/controller/MainController.java b/src/main/java/com/example/moim/main/controller/MainController.java index 1ccfade..b2b3646 100644 --- a/src/main/java/com/example/moim/main/controller/MainController.java +++ b/src/main/java/com/example/moim/main/controller/MainController.java @@ -22,6 +22,6 @@ public NoClubMainOutput noClubMainPage(@AuthenticationPrincipal UserDetailsImpl @GetMapping("/main/{clubId}") public MainOutput mainPage (@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @PathVariable Long clubId) { - return mainService.mainPage(clubId); + return mainService.mainPage(clubId, userDetailsImpl.getUser()); } } \ No newline at end of file diff --git a/src/main/java/com/example/moim/main/service/MainService.java b/src/main/java/com/example/moim/main/service/MainService.java index 6080845..db8cd95 100644 --- a/src/main/java/com/example/moim/main/service/MainService.java +++ b/src/main/java/com/example/moim/main/service/MainService.java @@ -1,6 +1,6 @@ package com.example.moim.main.service; -import com.example.moim.schedule.dto.ScheduleSearchInput; +import com.example.moim.schedule.dto.ScheduleSearchMonthInput; import com.example.moim.club.repository.ClubRepository; import com.example.moim.main.dto.MainOutput; import com.example.moim.main.dto.NoClubMainOutput; @@ -25,9 +25,11 @@ public NoClubMainOutput noClubMainPage (User user) { .stream().map(RecommendClubListOutput::new).toList(), null); } - public MainOutput mainPage(Long clubId) { + public MainOutput mainPage(Long clubId, User user) { return new MainOutput(clubRepository.findById(clubId).get(), - scheduleQueryService.findMonthlySchedulesWithFilter(new ScheduleSearchInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), - clubId, null, null))); + scheduleQueryService.findMonthlySchedulesWithFilter( + new ScheduleSearchMonthInput(Integer.parseInt(LocalDate.now().toString().replace("-", "")), + clubId, null, null), user) + ); } } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 9aca775..62c979d 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -6,7 +6,6 @@ import com.example.moim.schedule.service.ScheduleCommandService; import com.example.moim.schedule.service.ScheduleQueryService; import com.example.moim.user.dto.UserDetailsImpl; -import com.example.moim.user.entity.User; import com.example.moim.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -37,20 +36,20 @@ public BaseResponse updateSchedule(@RequestBody ScheduleUpdateIn } @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) - public BaseResponse> searchScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - List scheduleList = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); + public BaseResponse> searchScheduleListByMonth(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + List scheduleList = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchMonthInput, userDetailsImpl.getUser()); return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) - public BaseResponse> getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - List scheduleList = scheduleQueryService.findScheduleByDay(scheduleSearchInput); + public BaseResponse> searchScheduleListByDay(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + List scheduleList = scheduleQueryService.findScheduleByDay(scheduleSearchMonthInput, userDetailsImpl.getUser()); return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } @GetMapping("/schedules/{id}") - public BaseResponse getScheduleDetail(@PathVariable Long id) { - ScheduleDetailOutput scheduleDetail = scheduleQueryService.findScheduleDetail(id); + public BaseResponse getScheduleDetail(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + ScheduleDetailOutput scheduleDetail = scheduleQueryService.findScheduleDetail(id, userDetailsImpl.getUser()); return BaseResponse.onSuccess(scheduleDetail, ResponseCode.OK); } diff --git a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java index 2111aca..4b8efa7 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -4,7 +4,7 @@ import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleInput; import com.example.moim.schedule.dto.ScheduleOutput; -import com.example.moim.schedule.dto.ScheduleSearchInput; +import com.example.moim.schedule.dto.ScheduleSearchMonthInput; import com.example.moim.schedule.dto.ScheduleUpdateInput; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; @@ -25,13 +25,13 @@ public interface ScheduleControllerDocs { BaseResponse updateSchedule(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @PathVariable Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "한달 일정 조회", description = "카테고리는 친선 매치/정기 운동/대회/기타 중에 하나여야 합니다(띄어쓰기까지 포함)") - BaseResponse> searchScheduleList(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + BaseResponse> searchScheduleListByMonth(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "하루 일정 조회", description = "쿼리파라미터 예시: /schedule/day?date=20240910&clubId=6&search=친선 경기&category=친선 경기") - BaseResponse> getScheduleListByDay(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + BaseResponse> searchScheduleListByDay(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 세부 조회", description = "참가면 attendance = attend, 참가 취소는 absent, 투표 안하면 notVote") - BaseResponse getScheduleDetail(@PathVariable Long id); + BaseResponse getScheduleDetail(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 삭제") BaseResponse deleteSchedule(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchDayInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchDayInput.java new file mode 100644 index 0000000..a74923b --- /dev/null +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchDayInput.java @@ -0,0 +1,21 @@ +package com.example.moim.schedule.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Pattern; +import lombok.Builder; +import lombok.Data; + +@Data +public class ScheduleSearchDayInput { + + @Schema(description = "조회 기준 년월 (YYYYMM 형식, 예: 20240413)", example = "20240413") + @Pattern(regexp = "^[0-9]{6}$", message = "날짜는 YYYYMMDD 형식이어야 합니다.") + private final Integer date; + private final Long clubId; + + @Builder + public ScheduleSearchDayInput(Integer date, Long clubId) { + this.date = date; + this.clubId = clubId; + } +} diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java similarity index 82% rename from src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java rename to src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java index de5085e..89c1524 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java @@ -6,7 +6,7 @@ import lombok.Data; @Data -public class ScheduleSearchInput { +public class ScheduleSearchMonthInput { @Schema(description = "조회 기준 년월 (YYYYMM 형식, 예: 202404)", example = "202404") @Pattern(regexp = "^[0-9]{6}$", message = "날짜는 YYYYMM 형식이어야 합니다.") @@ -16,7 +16,7 @@ public class ScheduleSearchInput { private final String category; @Builder - public ScheduleSearchInput(Integer date, Long clubId, String search, String category) { + public ScheduleSearchMonthInput(Integer date, Long clubId, String search, String category) { this.date = date; this.clubId = clubId; this.search = search; diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java index 786207a..5399290 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java @@ -2,12 +2,13 @@ import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleOutput; -import com.example.moim.schedule.dto.ScheduleSearchInput; +import com.example.moim.schedule.dto.ScheduleSearchMonthInput; +import com.example.moim.user.entity.User; import java.util.List; public interface ScheduleQueryService { - List findMonthlySchedulesWithFilter(ScheduleSearchInput scheduleSearchInput); - List findScheduleByDay(ScheduleSearchInput scheduleSearchInput); - ScheduleDetailOutput findScheduleDetail(Long scheduleId); + List findMonthlySchedulesWithFilter(ScheduleSearchMonthInput scheduleSearchMonthInput, User user); + List findScheduleByDay(ScheduleSearchMonthInput scheduleSearchMonthInput, User user); + ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index 400bc09..c7915d6 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -4,12 +4,13 @@ import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.dto.MatchApplyClubOutput; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleOutput; -import com.example.moim.schedule.dto.ScheduleSearchInput; +import com.example.moim.schedule.dto.ScheduleSearchMonthInput; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; @@ -37,34 +38,38 @@ public class ScheduleQueryServiceImpl implements ScheduleQueryService { * TODO: 기획에 맞게, 스케줄 조회 기준 바꾸기 * User 가 가진 스케줄로 조회해야하는거 아닌가..? * 그럼 이거 구조 아예 다시 바꿔야 할 것 같은데... - * @param scheduleSearchInput + * @param scheduleSearchMonthInput * @return */ - public List findMonthlySchedulesWithFilter(ScheduleSearchInput scheduleSearchInput) { - Club club = getClub(scheduleSearchInput.getClubId()); + public List findMonthlySchedulesWithFilter(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { + Club club = getClub(scheduleSearchMonthInput.getClubId()); - int year = scheduleSearchInput.getDate() / 100; - log.info("year : {}", year); - int month = scheduleSearchInput.getDate() % 100; - log.info("month : {}", month); + getUserClub(club, user); // 권한 확인 + + int year = scheduleSearchMonthInput.getDate() / 100; + int month = scheduleSearchMonthInput.getDate() % 100; LocalDateTime startDate = LocalDateTime.of(year, month, 1, 0, 0, 0).minusDays(6); LocalDateTime endDate = LocalDateTime.of(year, month, Month.of(month).minLength(), 23, 59, 59).plusDays(6); return scheduleRepository.findByClubAndTime(club, startDate, endDate, - scheduleSearchInput.getSearch(), - scheduleSearchInput.getCategory()) + scheduleSearchMonthInput.getSearch(), + scheduleSearchMonthInput.getCategory()) .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } - public List findScheduleByDay(ScheduleSearchInput scheduleSearchInput) { - LocalDateTime searchDate = LocalDateTime.of(scheduleSearchInput.getDate() / 10000, (scheduleSearchInput.getDate() / 100) % 100, scheduleSearchInput.getDate() % 100, - 0, 0, 0); - Club club = getClub(scheduleSearchInput.getClubId()); + public List findScheduleByDay(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { + int year = scheduleSearchMonthInput.getDate() / 10000; + int month = (scheduleSearchMonthInput.getDate() / 100) % 100; + int day = scheduleSearchMonthInput.getDate() % 100; - return scheduleRepository.findByClubAndTime(club, - searchDate, searchDate.plusDays(1), scheduleSearchInput.getSearch(), scheduleSearchInput.getCategory()) + LocalDateTime searchDate = LocalDateTime.of(year, month, day, 0, 0, 0); + Club club = getClub(scheduleSearchMonthInput.getClubId()); + + getUserClub(club, user); // 권한 확인 + + return scheduleRepository.findByClubAndTime(club, searchDate, searchDate.plusDays(1), scheduleSearchMonthInput.getSearch(), scheduleSearchMonthInput.getCategory()) .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } @@ -73,9 +78,11 @@ public List findScheduleByDay(ScheduleSearchInput scheduleSearch * @param scheduleId * @return */ - public ScheduleDetailOutput findScheduleDetail(Long scheduleId) { + public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { Schedule schedule = getSchedule(scheduleId); + getUserClub(schedule.getClub(), user); // 권한 확인 + return new ScheduleDetailOutput(schedule, // scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java index 5d35d25..43bf176 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -2,7 +2,9 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.ClubRepository; +import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; import com.example.moim.match.entity.Match; import com.example.moim.match.entity.MatchApplication; @@ -10,6 +12,8 @@ import com.example.moim.schedule.dto.*; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.user.dto.SignupInput; +import com.example.moim.user.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -36,6 +40,8 @@ public class ScheduleQueryServiceImplTest { @Mock private ScheduleRepository scheduleRepository; @Mock + private UserClubRepository userClubRepository; + @Mock private MatchApplicationRepository matchApplicationRepository; @InjectMocks private ScheduleQueryServiceImpl scheduleQueryService; @@ -43,9 +49,13 @@ public class ScheduleQueryServiceImplTest { // 필요한 공동 객체 private ScheduleInput scheduleInput; private ClubInput clubInput; + private SignupInput signupInput; @BeforeEach void init() { + this.signupInput = SignupInput.builder().email("email").password("password") + .name("name").birthday("birthday").gender(Gender.WOMAN.getKoreanName()).build(); + this.scheduleInput = ScheduleInput.builder().clubId(1L).title("title").location("location") .startTime(LocalDateTime.of(2024, 12, 13, 12, 30, 0)) .endTime(LocalDateTime.of(2024, 12, 13, 17, 30, 0)) @@ -64,13 +74,16 @@ void init() { void findSchedule() { //given Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); + ScheduleSearchMonthInput scheduleSearchMonthInput = ScheduleSearchMonthInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchMonthInput, user); //then assertThat(result.size()).isEqualTo(1); @@ -86,12 +99,15 @@ void findSchedule() { void findSchedule_zero_schedule() { //given Club club = Club.from(clubInput, null); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + ScheduleSearchMonthInput scheduleSearchMonthInput = ScheduleSearchMonthInput.builder().date(202412).clubId(1L).search("title").category("soccer").build(); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of()); - List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchInput); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + List result = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchMonthInput, user); //then assertThat(result.size()).isEqualTo(0); @@ -104,13 +120,16 @@ void findSchedule_zero_schedule() { void findDaySchedule() { //given Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleSearchInput scheduleSearchInput = ScheduleSearchInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); + ScheduleSearchMonthInput scheduleSearchMonthInput = ScheduleSearchMonthInput.builder().date(20241211).clubId(1L).search("title").category("soccer").build(); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); when(scheduleRepository.findByClubAndTime(any(Club.class), any(LocalDateTime.class), any(LocalDateTime.class), any(String.class), any(String.class))).thenReturn(List.of(schedule)); - List result = scheduleQueryService.findScheduleByDay(scheduleSearchInput); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + List result = scheduleQueryService.findScheduleByDay(scheduleSearchMonthInput, user); //then assertThat(result.size()).isEqualTo(1); @@ -126,14 +145,17 @@ void findDaySchedule() { void findScheduleDetail() { //given Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); Schedule schedule = Schedule.from(club, scheduleInput); schedule.setCreatedDate(); schedule.setUpdatedDate(); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); when(matchApplicationRepository.findBySchedule(any(Schedule.class))).thenReturn(List.of(matchApplication)); - ScheduleDetailOutput result = scheduleQueryService.findScheduleDetail(1L); + ScheduleDetailOutput result = scheduleQueryService.findScheduleDetail(1L, user); //then assertThat(result.getMatchApplyClubList().size()).isEqualTo(1); assertThat(result.getTitle()).isEqualTo("title"); From 3f03800e356dfd4c9695349e0b70647c671bf160 Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 24 Apr 2025 13:54:24 +0900 Subject: [PATCH 17/33] =?UTF-8?q?merge(#33):=20develop=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=EC=99=80=20=EB=A8=B8=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 49 ++++++++++ .../club/controller/NoticeController.java | 44 ++++----- .../club/controller/NoticeControllerDocs.java | 14 ++- .../moim/club/dto/request/NoticeInput.java | 4 +- .../{request => response}/NoticeOutput.java | 2 +- .../club/repository/ClubRepositoryImpl.java | 2 +- .../club/repository/NoticeRepository.java | 7 +- .../repository/NoticeRepositoryCustom.java | 11 +++ .../club/repository/NoticeRepositoryImpl.java | 42 +++++++++ .../club/service/ClubCommandServiceImpl.java | 3 + .../club/service/NoticeCommandService.java | 4 +- .../service/NoticeCommandServiceImpl.java | 35 +++++--- .../moim/club/service/NoticeQueryService.java | 6 +- .../club/service/NoticeQueryServiceImpl.java | 32 ++++++- .../moim/config/security/SecurityConfig.java | 2 + .../schedule/dto/ScheduleDetailOutput.java | 6 +- .../service/ScheduleQueryServiceImpl.java | 3 +- .../service/NoticeCommandServiceImplTest.java | 28 +++++- .../service/NoticeQueryServiceImplTest.java | 89 ++++++++++++++++--- 19 files changed, 315 insertions(+), 68 deletions(-) rename src/main/java/com/example/moim/club/dto/{request => response}/NoticeOutput.java (92%) create mode 100644 src/main/java/com/example/moim/club/repository/NoticeRepositoryCustom.java create mode 100644 src/main/java/com/example/moim/club/repository/NoticeRepositoryImpl.java diff --git a/deploy/local.init.sql b/deploy/local.init.sql index 32ff194..cd39bf7 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -29,6 +29,46 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); +-- notice +INSERT INTO notice (id, created_date, updated_date, club_id, title, content) +values (default, '2024-12-10', null, 1, 'title', 'content'); +INSERT INTO notice (id, created_date, updated_date, club_id, title, content) +values (default, '2024-12-11', null, 1, 'title2', 'content2'); +INSERT INTO notice (id, created_date, updated_date, club_id, title, content) +values (default, '2024-12-12', null, 1, 'title3', 'content4'); + +-- 스케줄 생성(3번 동아리) +insert into `schedule` (id, club_id, title, loINSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) +VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'MAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'title nothing', '한양대학교'); +INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) +VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'WOMAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'titlenothing', '서울대학교'); +INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) +VALUES (default, null, null, 'GYEONGGI', 'THIRTIES', 'SOCIAL_GROUP', 'club_password', 'explanation', 'UNISEX', 'introduction', 'FUTSAL', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'title nothing', '연세대학교'); + +INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) +VALUES (1, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); +INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) +VALUES (2, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); +INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) +VALUES (3, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); + +INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) +VALUES (default, null, null, 'SEOUL', 'birthday', 'email', 'fcm_token', 'WOMAN', 180, null, 'RIGHT', 'ST', 'name', 'password', 'phone', 'refresh_token123456', 'USER', 'LM', 70); +INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) +VALUES (default, null, null, 'SEOUL', 'birthday', 'email', 'fcm_token', 'WOMAN', 180, null, 'LEFT', 'LM', 'name', 'password', 'phone', 'refresh_token12345678', 'USER', 'ST', 70); +INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) +VALUES (default, null, null, 'GYEONGGI', 'birthday', 'email', 'fcm_token', 'MAN', 180, null, 'RIGHT', 'GK', 'name', 'password', 'phone', 'refresh_token123456789', 'USER', 'LM', 70); +INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) +VALUES (default, null, null, 'GYEONGGI', 'birthday', 'email', 'fcm_token', 'MAN', 180, null, 'BOTH', 'MANAGER', 'name', 'password', 'phone', 'refresh_token12345678910', 'USER', 'GK', 70); + +INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) +VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 1, 1); +---- 3번 동아리에 가입한 유저들 +INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) +VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); +INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) +VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); + -- 스케줄 생성(3번 동아리) insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); @@ -39,4 +79,13 @@ values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03- insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0);cation, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) +values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file diff --git a/src/main/java/com/example/moim/club/controller/NoticeController.java b/src/main/java/com/example/moim/club/controller/NoticeController.java index 0191738..54d5eab 100644 --- a/src/main/java/com/example/moim/club/controller/NoticeController.java +++ b/src/main/java/com/example/moim/club/controller/NoticeController.java @@ -1,43 +1,45 @@ package com.example.moim.club.controller; import com.example.moim.club.dto.request.NoticeInput; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; import com.example.moim.club.service.NoticeCommandService; -import com.example.moim.club.service.NoticeCommandServiceImpl; import com.example.moim.club.service.NoticeQueryService; import com.example.moim.global.exception.BaseResponse; import com.example.moim.global.exception.ResponseCode; import com.example.moim.user.dto.UserDetailsImpl; +import com.example.moim.user.entity.User; +import com.example.moim.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Slice; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; -import java.util.List; @RestController @RequiredArgsConstructor public class NoticeController implements NoticeControllerDocs { private final NoticeCommandService noticeCommandService; private final NoticeQueryService noticeQueryService; + private final UserRepository userRepository; - /** - * FIXME: 응답 값이 무조건 있어야 함. user 의 권한 체크는 안해도 되나? - */ - @PostMapping("/notices") - public BaseResponse noticeSave(NoticeInput noticeInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - noticeCommandService.saveNotice(noticeInput); - return BaseResponse.onSuccess(null, ResponseCode.OK); + @PostMapping("/notice/{clubId}") + public BaseResponse saveNotice(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @ModelAttribute NoticeInput noticeInput, @PathVariable("clubId") Long clubId) { +// public BaseResponse saveNotice(@ModelAttribute NoticeInput noticeInput, @PathVariable("clubId") Long clubId) { + /** + * FIXME: 컨트롤러 테스트용 코드 + */ +// User user = userRepository.findById(3L).get(); + NoticeOutput noticeOutput = noticeCommandService.saveNotice(userDetailsImpl.getUser(), noticeInput, clubId); + return BaseResponse.onSuccess(noticeOutput, ResponseCode.OK); } - /** - * FIXME: 공지를 시간 순으로 정렬하지 않아도 되나? - * @return - */ - @GetMapping("/notices/{clubId}") - public BaseResponse> noticeSave(@PathVariable Long clubId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return BaseResponse.onSuccess(noticeQueryService.findNotice(clubId), ResponseCode.OK); + @GetMapping("/notice/{clubId}") + public BaseResponse> findNotices(@PathVariable Long clubId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @RequestParam("cursorId") Long cursorId) { +// public BaseResponse> findNotices(@PathVariable Long clubId, @RequestParam(value = "cursorId", required = false) Long cursorId) { + /** + * FIXME: 컨트롤러 테스트용 코드 + */ +// User user = userRepository.findById(1L).get(); + return BaseResponse.onSuccess(noticeQueryService.findNotice(userDetailsImpl.getUser(), clubId, cursorId), ResponseCode.OK); } } diff --git a/src/main/java/com/example/moim/club/controller/NoticeControllerDocs.java b/src/main/java/com/example/moim/club/controller/NoticeControllerDocs.java index eb7a706..cd0f5c6 100644 --- a/src/main/java/com/example/moim/club/controller/NoticeControllerDocs.java +++ b/src/main/java/com/example/moim/club/controller/NoticeControllerDocs.java @@ -1,21 +1,27 @@ package com.example.moim.club.controller; import com.example.moim.club.dto.request.NoticeInput; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; import com.example.moim.global.exception.BaseResponse; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @Tag(name = "모임 공지 api", description = "모임(club) 공지 api 분리") public interface NoticeControllerDocs { @Operation(summary = "공지 생성") - BaseResponse noticeSave(NoticeInput noticeInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + BaseResponse saveNotice(@AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @ModelAttribute NoticeInput noticeInput, @PathVariable("clubId") Long clubId); +// BaseResponse saveNotice(@ModelAttribute NoticeInput noticeInput, @PathVariable Long clubId); - @Operation(summary = "공지 조회") - BaseResponse> noticeSave(@PathVariable Long clubId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + @Operation(summary = "공지 조회, 최초 요청 시 cursorId null 로 보내기") + BaseResponse> findNotices(@PathVariable Long clubId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl, @RequestParam Long cursorId); +// BaseResponse> findNotices(@PathVariable Long clubId, @RequestParam Long cursorId); } diff --git a/src/main/java/com/example/moim/club/dto/request/NoticeInput.java b/src/main/java/com/example/moim/club/dto/request/NoticeInput.java index 64a8859..a9a27bd 100644 --- a/src/main/java/com/example/moim/club/dto/request/NoticeInput.java +++ b/src/main/java/com/example/moim/club/dto/request/NoticeInput.java @@ -7,13 +7,11 @@ @Data @NoArgsConstructor public class NoticeInput { - private Long clubId; private String title; private String content; @Builder - public NoticeInput(Long clubId, String title, String content) { - this.clubId = clubId; + public NoticeInput(String title, String content) { this.title = title; this.content = content; } diff --git a/src/main/java/com/example/moim/club/dto/request/NoticeOutput.java b/src/main/java/com/example/moim/club/dto/response/NoticeOutput.java similarity index 92% rename from src/main/java/com/example/moim/club/dto/request/NoticeOutput.java rename to src/main/java/com/example/moim/club/dto/response/NoticeOutput.java index 5e4267e..3633b87 100644 --- a/src/main/java/com/example/moim/club/dto/request/NoticeOutput.java +++ b/src/main/java/com/example/moim/club/dto/response/NoticeOutput.java @@ -1,4 +1,4 @@ -package com.example.moim.club.dto.request; +package com.example.moim.club.dto.response; import com.example.moim.club.entity.Notice; import lombok.Data; diff --git a/src/main/java/com/example/moim/club/repository/ClubRepositoryImpl.java b/src/main/java/com/example/moim/club/repository/ClubRepositoryImpl.java index f9908d7..7fd19ac 100644 --- a/src/main/java/com/example/moim/club/repository/ClubRepositoryImpl.java +++ b/src/main/java/com/example/moim/club/repository/ClubRepositoryImpl.java @@ -18,7 +18,7 @@ import static com.example.moim.club.entity.QClubSearch.clubSearch; import static org.springframework.util.StringUtils.hasText; -public class ClubRepositoryImpl implements ClubRepositoryCustom{ +public class ClubRepositoryImpl implements ClubRepositoryCustom { private final JPAQueryFactory queryFactory; public ClubRepositoryImpl(EntityManager em) { diff --git a/src/main/java/com/example/moim/club/repository/NoticeRepository.java b/src/main/java/com/example/moim/club/repository/NoticeRepository.java index b2547ad..0d38aaa 100644 --- a/src/main/java/com/example/moim/club/repository/NoticeRepository.java +++ b/src/main/java/com/example/moim/club/repository/NoticeRepository.java @@ -2,10 +2,13 @@ import com.example.moim.club.entity.Club; import com.example.moim.club.entity.Notice; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; -public interface NoticeRepository extends JpaRepository { - List findByClub(Club club); +public interface NoticeRepository extends JpaRepository, NoticeRepositoryCustom { +// Page findByClub(Club club, Pageable pageable); + } diff --git a/src/main/java/com/example/moim/club/repository/NoticeRepositoryCustom.java b/src/main/java/com/example/moim/club/repository/NoticeRepositoryCustom.java new file mode 100644 index 0000000..fb80b4a --- /dev/null +++ b/src/main/java/com/example/moim/club/repository/NoticeRepositoryCustom.java @@ -0,0 +1,11 @@ +package com.example.moim.club.repository; + +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.Notice; + +import java.util.List; + +public interface NoticeRepositoryCustom { + List findByCursor(Long cursor, int size, Club club); +} + diff --git a/src/main/java/com/example/moim/club/repository/NoticeRepositoryImpl.java b/src/main/java/com/example/moim/club/repository/NoticeRepositoryImpl.java new file mode 100644 index 0000000..94f9e79 --- /dev/null +++ b/src/main/java/com/example/moim/club/repository/NoticeRepositoryImpl.java @@ -0,0 +1,42 @@ +package com.example.moim.club.repository; + +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.Notice; +import com.example.moim.club.entity.QNotice; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +public class NoticeRepositoryImpl implements NoticeRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + public NoticeRepositoryImpl(EntityManager em) { + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public List findByCursor(Long cursor, int size, Club club) { + return queryFactory + .selectFrom(QNotice.notice) + .where( + ltCursor(cursor), + clubEq(club) + ) + .orderBy(QNotice.notice.id.desc()) + .limit(size + 1) // hasNext 판단용으로 1개 더 + .fetch(); + } + + private BooleanExpression ltCursor(Long cursor) { + return cursor != null ? QNotice.notice.id.lt(cursor) : null; + } + + private BooleanExpression clubEq(Club club) { + return club != null ? QNotice.notice.club.eq(club) : null; + } +} + diff --git a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java index 7b87cde..b7c4b48 100644 --- a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java @@ -83,6 +83,9 @@ public UserClubOutput saveClubUser(User user, ClubUserSaveInput clubUserSaveInpu } club.plusMemberCount(); UserClub userClub = userClubRepository.save(UserClub.createUserClub(user, club)); + /** + * TODO: 알림 보내는 것 새로운 방식에 맞춰서 다시 구현해야 함 + */ eventPublisher.publishEvent(new ClubJoinEvent(user, club)); return new UserClubOutput(userClub); } diff --git a/src/main/java/com/example/moim/club/service/NoticeCommandService.java b/src/main/java/com/example/moim/club/service/NoticeCommandService.java index 4d42d48..f6c3e0b 100644 --- a/src/main/java/com/example/moim/club/service/NoticeCommandService.java +++ b/src/main/java/com/example/moim/club/service/NoticeCommandService.java @@ -1,7 +1,9 @@ package com.example.moim.club.service; import com.example.moim.club.dto.request.NoticeInput; +import com.example.moim.club.dto.response.NoticeOutput; +import com.example.moim.user.entity.User; public interface NoticeCommandService { - void saveNotice(NoticeInput noticeInput); + NoticeOutput saveNotice(User user, NoticeInput noticeInput, Long clubId); } diff --git a/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java index 8423415..b42a3f5 100644 --- a/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java @@ -1,32 +1,45 @@ package com.example.moim.club.service; import com.example.moim.club.dto.request.NoticeInput; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; +import com.example.moim.club.entity.Club; import com.example.moim.club.entity.Notice; +import com.example.moim.club.entity.UserClub; import com.example.moim.club.exception.advice.ClubControllerAdvice; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.NoticeRepository; +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.user.entity.User; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.List; - @Slf4j @Service @RequiredArgsConstructor public class NoticeCommandServiceImpl implements NoticeCommandService { private final NoticeRepository noticeRepository; private final ClubRepository clubRepository; + private final UserClubRepository userClubRepository; + + public NoticeOutput saveNotice(User user, NoticeInput noticeInput, Long clubId) { + log.debug("saveNotice 진입"); + Club club = clubRepository.findById(clubId).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); + UserClub userClub = userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + + if (!userClub.getClubRole().equals(ClubRole.STAFF)) { // 공지 등록 권한 확인 + log.debug("권한 오류"); + throw new ClubControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); + } + + Notice notice = noticeRepository.save(Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent())); + + /** + * TODO: 알림 보내는 부분 구현해야함 + */ - /** - * transaction 필요하지 않음. save 작업 하나만 이루어지고, 나머지는 다 조회 연산이므로 - * save 함수 내부에 이미 transaction 적용되어 있음 - * @param noticeInput - */ - public void saveNotice(NoticeInput noticeInput) { - noticeRepository.save(Notice.createNotice(clubRepository.findById(noticeInput.getClubId()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_NOT_FOUND)), - noticeInput.getTitle(), noticeInput.getContent())); + return new NoticeOutput(notice); } } diff --git a/src/main/java/com/example/moim/club/service/NoticeQueryService.java b/src/main/java/com/example/moim/club/service/NoticeQueryService.java index 214db10..a763238 100644 --- a/src/main/java/com/example/moim/club/service/NoticeQueryService.java +++ b/src/main/java/com/example/moim/club/service/NoticeQueryService.java @@ -1,9 +1,11 @@ package com.example.moim.club.service; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; +import com.example.moim.user.entity.User; +import org.springframework.data.domain.Slice; import java.util.List; public interface NoticeQueryService { - List findNotice(Long clubId); + Slice findNotice(User user, Long clubId, Long cursorId); } diff --git a/src/main/java/com/example/moim/club/service/NoticeQueryServiceImpl.java b/src/main/java/com/example/moim/club/service/NoticeQueryServiceImpl.java index 02ce284..719afd4 100644 --- a/src/main/java/com/example/moim/club/service/NoticeQueryServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/NoticeQueryServiceImpl.java @@ -1,11 +1,18 @@ package com.example.moim.club.service; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; +import com.example.moim.club.entity.Club; +import com.example.moim.club.entity.Notice; +import com.example.moim.club.entity.UserClub; import com.example.moim.club.exception.advice.ClubControllerAdvice; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.NoticeRepository; +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.user.entity.User; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.*; import org.springframework.stereotype.Service; import java.util.List; @@ -14,10 +21,29 @@ @RequiredArgsConstructor public class NoticeQueryServiceImpl implements NoticeQueryService { + private static final int SIZE = 20; + private final NoticeRepository noticeRepository; private final ClubRepository clubRepository; + private final UserClubRepository userClubRepository; + + public Slice findNotice(User user, Long clubId, Long cursorId) { + // 가입된 회원인지 확인, 가입되지 않은 회원이면 공지 열람 권한이 없는 것 + Club club = clubRepository.findById(clubId).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); + UserClub userClub = userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); + + List notices = noticeRepository.findByCursor(cursorId, SIZE, club); + + boolean hasNext = notices.size() > SIZE; + + if (hasNext) { + notices.remove(SIZE); + } + + List outputs = notices.stream() + .map(NoticeOutput::new) + .toList(); - public List findNotice(Long clubId) { - return noticeRepository.findByClub(clubRepository.findById(clubId).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_NOT_FOUND))).stream().map(NoticeOutput::new).toList(); + return new SliceImpl<>(outputs, PageRequest.of(0, SIZE), hasNext); } } diff --git a/src/main/java/com/example/moim/config/security/SecurityConfig.java b/src/main/java/com/example/moim/config/security/SecurityConfig.java index cceafa3..54c7a6a 100644 --- a/src/main/java/com/example/moim/config/security/SecurityConfig.java +++ b/src/main/java/com/example/moim/config/security/SecurityConfig.java @@ -71,6 +71,8 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { httpSecurity.csrf(csrf -> csrf .ignoringRequestMatchers("/clubs/**", "/schedules/**", "/notices/**") ); + httpSecurity.csrf(csrf -> csrf.ignoringRequestMatchers("/notice/**")); + //From 로그인 방식 disable httpSecurity.formLogin(AbstractHttpConfigurer::disable); //http basic 인증 방식 disable diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index 378b42c..c37d65a 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -13,7 +13,7 @@ public class ScheduleDetailOutput { private Long id; private String title; private String deadline; - private Boolean isClose; + private Boolean isClose; // 투표 마감 private String location; private String period; private int minPeople; @@ -22,7 +22,7 @@ public class ScheduleDetailOutput { private int attend; private int nonAttend; // List ScheduleUserList; - List MatchApplyClubList; + private List MatchApplyClubList; public ScheduleDetailOutput(Schedule schedule, List MatchApplyClubOutputList) { this.id = schedule.getId(); @@ -30,7 +30,7 @@ public ScheduleDetailOutput(Schedule schedule, List MatchA this.deadline = schedule.getCreatedDate().plusDays(5).format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm")); if (LocalDateTime.now().isBefore(schedule.getCreatedDate().plusDays(5))) { this.isClose = schedule.getIsClose(); - } else {//마감일 전이면 마감 상태 응답, 지났으면 무조건 마감한것으로 응답 + } else { //마감일 전이면 마감 상태 응답, 지났으면 무조건 마감한것으로 응답 this.isClose = true; } this.location = schedule.getLocation(); diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index c7915d6..a536e3a 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -74,7 +74,7 @@ public List findScheduleByDay(ScheduleSearchMonthInput scheduleS } /** - * FIXME: 세부 일정 페이지 보고 데이터 넘겨주기. 댓글도 포함되어야 함 + * FIXME: 세부 일정 페이지 보고 데이터 넘겨주기. 댓글도 포함되어야 함? * @param scheduleId * @return */ @@ -84,7 +84,6 @@ public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { getUserClub(schedule.getClub(), user); // 권한 확인 return new ScheduleDetailOutput(schedule, -// scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); } diff --git a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java index 23e3101..d729b3e 100644 --- a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java @@ -5,7 +5,10 @@ import com.example.moim.club.entity.*; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.NoticeRepository; +import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; +import com.example.moim.user.dto.SignupInput; +import com.example.moim.user.entity.User; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,8 +17,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDateTime; import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -29,11 +34,15 @@ class NoticeCommandServiceImplTest { private NoticeRepository noticeRepository; @Mock private ClubRepository clubRepository; + @Mock + private UserClubRepository userClubRepository; @InjectMocks private NoticeCommandServiceImpl noticeCommandServiceImpl; private ClubInput clubInput; private NoticeInput noticeInput; + private SignupInput signupInput; + @BeforeEach void init() { // Club @@ -56,7 +65,17 @@ void init() { .clubPassword(clubPassword).profileImg(profileImg).mainUniformColor(mainUniformColor).subUniformColor(subUniformColor).build(); // Notice - this.noticeInput = NoticeInput.builder().title("notice title").content("notice content").clubId(1L).build(); + this.noticeInput = NoticeInput.builder().title("notice title").content("notice content").build(); + + // User + this.signupInput = SignupInput.builder() + .email("email") + .phone("phone") + .birthday("2000-08-28") + .name("name") + .password("password") + .gender(Gender.WOMAN.getKoreanName()) + .build(); } @Test @DisplayName("공지를 저장할 수 있다") @@ -64,10 +83,15 @@ void saveNotice() { //given Club club = Club.from(clubInput, null); Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); + // notice - createAt 강제로 주입하기 + ReflectionTestUtils.setField(notice, "createdDate", LocalDateTime.now()); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); //when when(noticeRepository.save(any(Notice.class))).thenReturn(notice); when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - noticeCommandServiceImpl.saveNotice(noticeInput); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + noticeCommandServiceImpl.saveNotice(user, noticeInput, 1L); //then verify(noticeRepository, times(1)).save(any(Notice.class)); verify(clubRepository, times(1)).findById(any(Long.class)); diff --git a/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java b/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java index 91726ef..6bc4c2d 100644 --- a/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java @@ -2,11 +2,15 @@ import com.example.moim.club.dto.request.ClubInput; import com.example.moim.club.dto.request.NoticeInput; -import com.example.moim.club.dto.request.NoticeOutput; +import com.example.moim.club.dto.response.NoticeOutput; import com.example.moim.club.entity.*; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.NoticeRepository; +import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; +import com.example.moim.user.dto.SignupInput; +import com.example.moim.user.entity.User; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -14,9 +18,13 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.domain.Slice; import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @@ -25,17 +33,21 @@ import static org.mockito.Mockito.*; import static org.mockito.Mockito.times; +@Slf4j @ExtendWith(MockitoExtension.class) public class NoticeQueryServiceImplTest { @Mock private NoticeRepository noticeRepository; @Mock private ClubRepository clubRepository; + @Mock + private UserClubRepository userClubRepository; @InjectMocks private NoticeQueryServiceImpl noticeQueryService; private ClubInput clubInput; private NoticeInput noticeInput; + private SignupInput signupInput; @BeforeEach void init() { @@ -59,7 +71,17 @@ void init() { .clubPassword(clubPassword).profileImg(profileImg).mainUniformColor(mainUniformColor).subUniformColor(subUniformColor).build(); // Notice - this.noticeInput = NoticeInput.builder().title("notice title").content("notice content").clubId(1L).build(); + this.noticeInput = NoticeInput.builder().title("notice title").content("notice content").build(); + + // User + this.signupInput = SignupInput.builder() + .email("email") + .phone("phone") + .birthday("2000-08-28") + .name("name") + .password("password") + .gender(Gender.WOMAN.getKoreanName()) + .build(); } @Test @@ -68,18 +90,23 @@ void findNotice() { //given Club club = Club.from(clubInput, null); Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); notice.setCreatedDate(); notice.setUpdatedDate(); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - when(noticeRepository.findByClub(any(Club.class))).thenReturn(List.of(notice)); - List result = noticeQueryService.findNotice(1L); + when(noticeRepository.findByCursor(any(Long.class), any(Integer.class), any(Club.class))).thenReturn(List.of(notice)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + Slice result = noticeQueryService.findNotice(user, 1L, 0L); + //then - assertThat(result.size()).isEqualTo(1); - assertThat(result.get(0).getTitle()).isEqualTo(noticeInput.getTitle()); - assertThat(result.get(0).getContent()).isEqualTo(noticeInput.getContent()); + assertThat(result.getSize()).isEqualTo(20); + assertThat(result.getNumberOfElements()).isEqualTo(1); + assertThat(result.hasNext()).isFalse(); + assertThat(result.getContent().get(0).getTitle()).isEqualTo("notice title"); verify(clubRepository, times(1)).findById(any(Long.class)); - verify(noticeRepository, times(1)).findByClub(any(Club.class)); + verify(noticeRepository, times(1)).findByCursor(any(Long.class), any(Integer.class), any(Club.class)); } @Test @@ -87,14 +114,52 @@ void findNotice() { void findNotice_zero_notice() { //given Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - when(noticeRepository.findByClub(any(Club.class))).thenReturn(List.of()); - List result = noticeQueryService.findNotice(1L); + when(noticeRepository.findByCursor(any(Long.class), any(Integer.class), any(Club.class))).thenReturn(List.of()); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + Slice result = noticeQueryService.findNotice(user, 1L, 0L); + //then + assertThat(result.getSize()).isEqualTo(20); + assertThat(result.getNumberOfElements()).isEqualTo(0); + assertThat(result.hasNext()).isFalse(); + assertThat(result.hasContent()).isFalse(); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(noticeRepository, times(1)).findByCursor(any(Long.class), any(Integer.class), any(Club.class)); + } + + @Test + @DisplayName("동아리에 등록된 공지를 조회하면 나중에 저장된 순으로 정렬되어 있다.") + void findNotice_sort() { + //given + Club club = Club.from(clubInput, null); + Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); + ReflectionTestUtils.setField(notice, "id", 1L); + LocalDateTime localDateTime = LocalDateTime.now(); + ReflectionTestUtils.setField(notice, "createdDate", localDateTime); + Notice notice2 = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); + ReflectionTestUtils.setField(notice2, "id", 2L); + LocalDateTime localDateTime2 = LocalDateTime.now(); + ReflectionTestUtils.setField(notice2, "createdDate", localDateTime2); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + + //when + when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); + when(noticeRepository.findByCursor(any(Long.class), any(Integer.class), any(Club.class))).thenReturn(List.of(notice, notice2)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + Slice result = noticeQueryService.findNotice(user, 1L, 0L); + //then - assertThat(result.size()).isEqualTo(0); + assertThat(result.getSize()).isEqualTo(20); + assertThat(result.getNumberOfElements()).isEqualTo(2); + assertThat(result.hasNext()).isFalse(); + assertThat(result.getContent().get(0).getCreatedDate()).isEqualTo(LocalDate.from(localDateTime2)); + assertThat(result.getContent().get(1).getCreatedDate()).isEqualTo(LocalDate.from(localDateTime)); verify(clubRepository, times(1)).findById(any(Long.class)); - verify(noticeRepository, times(1)).findByClub(any(Club.class)); + verify(noticeRepository, times(1)).findByCursor(any(Long.class), any(Integer.class), any(Club.class)); } } From 20f939811a4473ae449660f47f1e7a0ff99bd9ce Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 24 Apr 2025 15:25:09 +0900 Subject: [PATCH 18/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20-?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=EC=88=98=20=EB=B6=80=EB=B6=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20=EC=A0=95=EA=B8=B0=EC=9A=B4=EB=8F=99,=20?= =?UTF-8?q?=EB=8C=80=ED=9A=8C,=20=EC=B9=9C=EC=84=A0=20=EB=A7=A4=EC=B9=98,?= =?UTF-8?q?=20=EA=B8=B0=ED=83=80=20=EB=B6=80=EB=B6=84=EB=A7=8C=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=EB=90=9C=20=EA=B2=83(=EC=8B=A4=EC=A0=9C=20=EB=A7=A4?= =?UTF-8?q?=EC=B9=98=20=EB=B6=80=EB=B6=84=EC=9D=80=20=EB=A6=AC=EB=8B=A4?= =?UTF-8?q?=EC=9D=B4=EB=A0=89=ED=8A=B8=20=EB=93=B1=20=EC=97=AC=EB=9F=AC=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EA=B3=A0=EB=AF=BC=20=EC=A4=91)=20-=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=EC=97=90=20=EC=86=8C=EC=86=8D=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=82=AC=EB=9E=8C=EC=9D=B4=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=A9=B4=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=98=EB=8A=94=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 32 +++++++------------ .../schedule/dto/ScheduleDetailOutput.java | 20 ++++++++---- .../dto/ScheduleMatchApplyClubOutput.java} | 6 ++-- .../moim/schedule/entity/Schedule.java | 6 ++++ .../service/ScheduleQueryServiceImpl.java | 10 +++--- .../service/ScheduleQueryServiceImplTest.java | 25 ++++++++++++--- .../sql/comment-repository-test-data.sql | 8 ++--- .../schedule-vote-repository-test-data.sql | 8 ++--- 8 files changed, 68 insertions(+), 47 deletions(-) rename src/main/java/com/example/moim/{match/dto/MatchApplyClubOutput.java => schedule/dto/ScheduleMatchApplyClubOutput.java} (52%) diff --git a/deploy/local.init.sql b/deploy/local.init.sql index cd39bf7..a1c72cc 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -37,8 +37,7 @@ values (default, '2024-12-11', null, 1, 'title2', 'content2'); INSERT INTO notice (id, created_date, updated_date, club_id, title, content) values (default, '2024-12-12', null, 1, 'title3', 'content4'); --- 스케줄 생성(3번 동아리) -insert into `schedule` (id, club_id, title, loINSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) +INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'MAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'title nothing', '한양대학교'); INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'WOMAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'titlenothing', '서울대학교'); @@ -70,22 +69,13 @@ INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, mat VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성(3번 동아리) -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0);cation, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); \ No newline at end of file +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 1); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 10); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 5); \ No newline at end of file diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index c37d65a..798b39a 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -1,7 +1,6 @@ package com.example.moim.schedule.dto; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.match.dto.MatchApplyClubOutput; import lombok.Data; import java.time.LocalDateTime; @@ -13,18 +12,25 @@ public class ScheduleDetailOutput { private Long id; private String title; private String deadline; + private int viewCount; + private String category; private Boolean isClose; // 투표 마감 - private String location; + // 일정 정보 + private String location; // 주소 private String period; + private String note; // 기타 참고 사항 + // 참가 투표 private int minPeople; - private String category; - private String note; private int attend; private int nonAttend; // List ScheduleUserList; - private List MatchApplyClubList; - public ScheduleDetailOutput(Schedule schedule, List MatchApplyClubOutputList) { + /** + * TODO: PM 답변에 따라서 상대 팀 정보 추가로 주는지 마는지 반영해서 구현 완료하기 + */ +// private List MatchApplyClubList; + + public ScheduleDetailOutput(Schedule schedule) { this.id = schedule.getId(); this.title = schedule.getTitle(); this.deadline = schedule.getCreatedDate().plusDays(5).format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm")); @@ -41,7 +47,7 @@ public ScheduleDetailOutput(Schedule schedule, List MatchA this.note = schedule.getNote(); this.attend = schedule.getAttend(); this.nonAttend = schedule.getNonAttend(); - this.MatchApplyClubList = MatchApplyClubOutputList; + this.viewCount = schedule.getViewCount(); } // public ScheduleDetailOutput(Schedule schedule, List ScheduleUserOutputList, List MatchApplyClubOutputList) { diff --git a/src/main/java/com/example/moim/match/dto/MatchApplyClubOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleMatchApplyClubOutput.java similarity index 52% rename from src/main/java/com/example/moim/match/dto/MatchApplyClubOutput.java rename to src/main/java/com/example/moim/schedule/dto/ScheduleMatchApplyClubOutput.java index ff2f4d6..f78ba19 100644 --- a/src/main/java/com/example/moim/match/dto/MatchApplyClubOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleMatchApplyClubOutput.java @@ -1,13 +1,13 @@ -package com.example.moim.match.dto; +package com.example.moim.schedule.dto; import com.example.moim.match.entity.MatchApplication; import lombok.Data; @Data -public class MatchApplyClubOutput { +public class ScheduleMatchApplyClubOutput { private String title; - public MatchApplyClubOutput(MatchApplication matchApplication) { + public ScheduleMatchApplyClubOutput(MatchApplication matchApplication) { this.title = matchApplication.getClub().getTitle(); } } diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index c51e6bf..68d0172 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -36,6 +36,7 @@ public class Schedule extends BaseEntity { private int attend; private int nonAttend; private Boolean isClose; + private int viewCount; @OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE) private List comment = new ArrayList<>(); @@ -55,6 +56,7 @@ public static Schedule from(Club club, ScheduleInput scheduleInput) { schedule.attend = 0; schedule.nonAttend = 0; schedule.isClose = false; + schedule.viewCount = 0; return schedule; } @@ -113,6 +115,10 @@ public void close() { this.isClose = true; } + public void increaseViewCount() { + this.viewCount += 1; + } + private AttendanceType getAttendanceType(String attendance) { return AttendanceType.fromKoreanName(attendance).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_ATTENDANCE_TYPE)); } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index a536e3a..610fc1c 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -4,9 +4,7 @@ import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; -import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; -import com.example.moim.match.dto.MatchApplyClubOutput; import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleOutput; @@ -18,6 +16,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.time.Month; @@ -78,13 +77,16 @@ public List findScheduleByDay(ScheduleSearchMonthInput scheduleS * @param scheduleId * @return */ + @Transactional public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { Schedule schedule = getSchedule(scheduleId); + // 조회 수 증가 반영 + schedule.increaseViewCount(); + getUserClub(schedule.getClub(), user); // 권한 확인 - return new ScheduleDetailOutput(schedule, - matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); + return new ScheduleDetailOutput(schedule); } private Club getClub(Long clubId) { diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java index 43bf176..18bac08 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -11,6 +11,7 @@ import com.example.moim.match.repository.MatchApplicationRepository; import com.example.moim.schedule.dto.*; import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; import com.example.moim.user.dto.SignupInput; import com.example.moim.user.entity.User; @@ -28,6 +29,7 @@ import java.util.Optional; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.mockito.Mockito.times; @@ -147,20 +149,35 @@ void findScheduleDetail() { Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); - MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); Schedule schedule = Schedule.from(club, scheduleInput); schedule.setCreatedDate(); schedule.setUpdatedDate(); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - when(matchApplicationRepository.findBySchedule(any(Schedule.class))).thenReturn(List.of(matchApplication)); ScheduleDetailOutput result = scheduleQueryService.findScheduleDetail(1L, user); //then - assertThat(result.getMatchApplyClubList().size()).isEqualTo(1); assertThat(result.getTitle()).isEqualTo("title"); assertThat(result.getCategory()).isEqualTo("정기 운동"); verify(scheduleRepository, times(1)).findById(any(Long.class)); - verify(matchApplicationRepository, times(1)).findBySchedule(any(Schedule.class)); + } + + @Test + @DisplayName("스케줄은 해당 모임에 소속되지 않은 사람이 조회하면 예외가 발생한다.") + void findScheduleDetail_non_member() { + //given + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + Schedule schedule = Schedule.from(club, scheduleInput); + schedule.setCreatedDate(); + schedule.setUpdatedDate(); + //when + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.empty()); + //then + Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { + scheduleQueryService.findScheduleDetail(1L, user); + }); + assertThat(exception.getMessage()).isEqualTo("가입되지 않은 회원입니다."); } } diff --git a/src/test/resources/sql/comment-repository-test-data.sql b/src/test/resources/sql/comment-repository-test-data.sql index 9ef9177..135d628 100644 --- a/src/test/resources/sql/comment-repository-test-data.sql +++ b/src/test/resources/sql/comment-repository-test-data.sql @@ -25,10 +25,10 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성 -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 5); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 2); -- 댓글 생성 insert into `comment` (id, user_id, schedule_id, contents) values (1, 3, 1, '회사 면접이 잡혀있어서 못 갑니다'); diff --git a/src/test/resources/sql/schedule-vote-repository-test-data.sql b/src/test/resources/sql/schedule-vote-repository-test-data.sql index f994d2d..3c11d11 100644 --- a/src/test/resources/sql/schedule-vote-repository-test-data.sql +++ b/src/test/resources/sql/schedule-vote-repository-test-data.sql @@ -22,10 +22,10 @@ VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); -- 스케줄 생성 -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close) -values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (1, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 5); +insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (2, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 3); -- 댓글 생성 insert into `comment` (id, user_id, schedule_id, contents) values (1, 3, 1, '회사 면접이 잡혀있어서 못 갑니다'); From fb98f8eb59e76bba11c02623ccf19346d409fca3 Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 24 Apr 2025 15:46:28 +0900 Subject: [PATCH 19/33] =?UTF-8?q?refactor(#33):=20schedule,=20club=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=95=8C=EB=A6=BC=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=84=B0=EB=A7=81=20=EB=B0=98=EC=98=81=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=ED=91=B8=EC=89=AC=20=EC=95=8C=EB=A6=BC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../club/service/ClubCommandServiceImpl.java | 4 +- .../service/NoticeCommandServiceImpl.java | 9 +++-- .../notification/dto/NoticeSaveEvent.java | 15 ++++++++ .../notification/dto/ScheduleDeleteEvent.java | 16 ++++++++ .../notification/dto/ScheduleUpdateEvent.java | 16 ++++++++ .../notification/entity/NotificationType.java | 3 ++ .../NoticeSaveNotificationEventHandler.java | 38 +++++++++++++++++++ ...cheduleDeleteNotificationEventHandler.java | 36 ++++++++++++++++++ ...cheduleUpdateNotificationEventHandler.java | 34 +++++++++++++++++ .../service/ScheduleCommandServiceImpl.java | 19 +++------- 10 files changed, 171 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/example/moim/notification/dto/NoticeSaveEvent.java create mode 100644 src/main/java/com/example/moim/notification/dto/ScheduleDeleteEvent.java create mode 100644 src/main/java/com/example/moim/notification/dto/ScheduleUpdateEvent.java create mode 100644 src/main/java/com/example/moim/notification/service/NoticeSaveNotificationEventHandler.java create mode 100644 src/main/java/com/example/moim/notification/service/ScheduleDeleteNotificationEventHandler.java create mode 100644 src/main/java/com/example/moim/notification/service/ScheduleUpdateNotificationEventHandler.java diff --git a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java index b7c4b48..f438703 100644 --- a/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/ClubCommandServiceImpl.java @@ -83,9 +83,7 @@ public UserClubOutput saveClubUser(User user, ClubUserSaveInput clubUserSaveInpu } club.plusMemberCount(); UserClub userClub = userClubRepository.save(UserClub.createUserClub(user, club)); - /** - * TODO: 알림 보내는 것 새로운 방식에 맞춰서 다시 구현해야 함 - */ + eventPublisher.publishEvent(new ClubJoinEvent(user, club)); return new UserClubOutput(userClub); } diff --git a/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java index b42a3f5..b345b52 100644 --- a/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java @@ -11,10 +11,13 @@ import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.notification.dto.NoticeSaveEvent; import com.example.moim.user.entity.User; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Slf4j @Service @@ -23,7 +26,9 @@ public class NoticeCommandServiceImpl implements NoticeCommandService { private final NoticeRepository noticeRepository; private final ClubRepository clubRepository; private final UserClubRepository userClubRepository; + private final ApplicationEventPublisher eventPublisher; + @Transactional public NoticeOutput saveNotice(User user, NoticeInput noticeInput, Long clubId) { log.debug("saveNotice 진입"); Club club = clubRepository.findById(clubId).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.CLUB_NOT_FOUND)); @@ -36,9 +41,7 @@ public NoticeOutput saveNotice(User user, NoticeInput noticeInput, Long clubId) Notice notice = noticeRepository.save(Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent())); - /** - * TODO: 알림 보내는 부분 구현해야함 - */ + eventPublisher.publishEvent(new NoticeSaveEvent(user, club)); return new NoticeOutput(notice); } diff --git a/src/main/java/com/example/moim/notification/dto/NoticeSaveEvent.java b/src/main/java/com/example/moim/notification/dto/NoticeSaveEvent.java new file mode 100644 index 0000000..b22a831 --- /dev/null +++ b/src/main/java/com/example/moim/notification/dto/NoticeSaveEvent.java @@ -0,0 +1,15 @@ +package com.example.moim.notification.dto; + +import com.example.moim.club.entity.Club; +import com.example.moim.user.entity.User; +import lombok.Data; + +@Data +public class NoticeSaveEvent { + User user; + Club club; + public NoticeSaveEvent(User user, Club club) { + this.user = user; + this.club = club; + } +} diff --git a/src/main/java/com/example/moim/notification/dto/ScheduleDeleteEvent.java b/src/main/java/com/example/moim/notification/dto/ScheduleDeleteEvent.java new file mode 100644 index 0000000..8d69ed4 --- /dev/null +++ b/src/main/java/com/example/moim/notification/dto/ScheduleDeleteEvent.java @@ -0,0 +1,16 @@ +package com.example.moim.notification.dto; + +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.user.entity.User; +import lombok.Data; + +@Data +public class ScheduleDeleteEvent { + private Schedule schedule; + private User user; + + public ScheduleDeleteEvent(Schedule schedule, User user) { + this.schedule = schedule; + this.user = user; + } +} diff --git a/src/main/java/com/example/moim/notification/dto/ScheduleUpdateEvent.java b/src/main/java/com/example/moim/notification/dto/ScheduleUpdateEvent.java new file mode 100644 index 0000000..16f5f1f --- /dev/null +++ b/src/main/java/com/example/moim/notification/dto/ScheduleUpdateEvent.java @@ -0,0 +1,16 @@ +package com.example.moim.notification.dto; + +import com.example.moim.schedule.entity.Schedule; +import com.example.moim.user.entity.User; +import lombok.Data; + +@Data +public class ScheduleUpdateEvent { + private Schedule schedule; + private User user; + + public ScheduleUpdateEvent(Schedule schedule, User user) { + this.schedule = schedule; + this.user = user; + } +} diff --git a/src/main/java/com/example/moim/notification/entity/NotificationType.java b/src/main/java/com/example/moim/notification/entity/NotificationType.java index 937e9ed..605fab7 100644 --- a/src/main/java/com/example/moim/notification/entity/NotificationType.java +++ b/src/main/java/com/example/moim/notification/entity/NotificationType.java @@ -9,6 +9,8 @@ public enum NotificationType { CLUB_JOIN("클럽 가입", "%s님이 %s에 가입했습니다."), SCHEDULE_SAVE("일정 등록", "%s 일정이 등록되었습니다. \n 참가 여부를 투표해주세요!!"), + SCHEDULE_UPDATE("일정 변경", "%s 일정이 변경되었습니다. \n 확인해주세요!!"), + SCHEDULE_DELETE("일정 취소", "%s 일정이 취소되었습니다. \n 확인해주세요!!"), SCHEDULE_REMINDER("일정 하루 전", "내일 %s 일정이 있습니다."), SCHEDULE_ENCOURAGE("투표 독려", "%s 일정이 참가투표가 곧 마감됩니다.\n 참가 여부를 투표해주세요!!"), SCHEDULE_JOIN("일정 참여", "%s 일정에 참여했습니다."), @@ -22,6 +24,7 @@ public enum NotificationType { MATCH_FAILED_UNSELECTED("매치 실패", "<%s> 매치 등록 클럽이 다른 클럽을 선택했어요\uD83E\uDEE3\n 다음에 다시 신청해주세요!"), MATCH_CANCEL_USER("매치 취소", "<%s> 매치가 취소되었습니다.\n 다음에 다시 신청해주세요!"), MATCH_CANCEL_CLUB("매치 취소", "<%s> 매치가 취소되었습니다.\n 다음에 다시 신청해주세요!"), + NOTICE_SAVE("공지 등록", "%s 공지가 등록되었습니다. \n 공지를 확인해주세요!!"), ; private final String title; diff --git a/src/main/java/com/example/moim/notification/service/NoticeSaveNotificationEventHandler.java b/src/main/java/com/example/moim/notification/service/NoticeSaveNotificationEventHandler.java new file mode 100644 index 0000000..587b2b8 --- /dev/null +++ b/src/main/java/com/example/moim/notification/service/NoticeSaveNotificationEventHandler.java @@ -0,0 +1,38 @@ +package com.example.moim.notification.service; + +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.notification.dto.ClubJoinEvent; +import com.example.moim.notification.dto.NoticeSaveEvent; +import com.example.moim.notification.entity.NotificationEntity; +import com.example.moim.notification.entity.NotificationType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class NoticeSaveNotificationEventHandler implements NotificationEventHandler { + + private final UserClubRepository userClubRepository; + + @Override + public boolean canHandle(Object event) { + return event instanceof NoticeSaveEvent; + } + + @Override + public List handle(NoticeSaveEvent event) { + return userClubRepository.findAllByClub(event.getClub()) + .stream() + .map(userClub -> NotificationEntity.create(userClub.getUser() + , NotificationType.NOTICE_SAVE + , NotificationType.NOTICE_SAVE.formatMessage( + event.getUser().getName(), + event.getClub().getTitle()) + , event.getClub().getTitle() + , event.getClub().getId()) + ) + .toList(); + } +} diff --git a/src/main/java/com/example/moim/notification/service/ScheduleDeleteNotificationEventHandler.java b/src/main/java/com/example/moim/notification/service/ScheduleDeleteNotificationEventHandler.java new file mode 100644 index 0000000..a6cb95e --- /dev/null +++ b/src/main/java/com/example/moim/notification/service/ScheduleDeleteNotificationEventHandler.java @@ -0,0 +1,36 @@ +package com.example.moim.notification.service; + +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.notification.dto.ScheduleDeleteEvent; +import com.example.moim.notification.dto.ScheduleSaveEvent; +import com.example.moim.notification.entity.NotificationEntity; +import com.example.moim.notification.entity.NotificationType; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class ScheduleDeleteNotificationEventHandler implements NotificationEventHandler { + + private final UserClubRepository userClubRepository; + + @Override + public boolean canHandle(Object event) { + return event instanceof ScheduleDeleteEvent; + } + + @Override + public List handle(ScheduleDeleteEvent event) { + return userClubRepository.findAllByClub(event.getSchedule().getClub()) + .stream() + .map(userClub -> NotificationEntity.create(event.getUser() + , NotificationType.SCHEDULE_DELETE + , NotificationType.SCHEDULE_DELETE.formatMessage(event.getSchedule().getTitle()) + , event.getSchedule().getTitle() + , event.getSchedule().getId() + )) + .toList(); + } +} diff --git a/src/main/java/com/example/moim/notification/service/ScheduleUpdateNotificationEventHandler.java b/src/main/java/com/example/moim/notification/service/ScheduleUpdateNotificationEventHandler.java new file mode 100644 index 0000000..3c2737c --- /dev/null +++ b/src/main/java/com/example/moim/notification/service/ScheduleUpdateNotificationEventHandler.java @@ -0,0 +1,34 @@ +package com.example.moim.notification.service; + +import com.example.moim.club.repository.UserClubRepository; +import com.example.moim.notification.dto.ScheduleSaveEvent; +import com.example.moim.notification.dto.ScheduleUpdateEvent; +import com.example.moim.notification.entity.NotificationEntity; +import com.example.moim.notification.entity.NotificationType; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@RequiredArgsConstructor +public class ScheduleUpdateNotificationEventHandler implements NotificationEventHandler { + + private final UserClubRepository userClubRepository; + + @Override + public boolean canHandle(Object event) { + return event instanceof ScheduleUpdateEvent; + } + + @Override + public List handle(ScheduleUpdateEvent event) { + return userClubRepository.findAllByClub(event.getSchedule().getClub()) + .stream() + .map(userClub -> NotificationEntity.create(event.getUser() + , NotificationType.SCHEDULE_UPDATE + , NotificationType.SCHEDULE_UPDATE.formatMessage(event.getSchedule().getTitle()) + , event.getSchedule().getTitle() + , event.getSchedule().getId() + )) + .toList(); + } +} diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java index 6b70d49..545c4bd 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -5,6 +5,7 @@ import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.notification.dto.ScheduleDeleteEvent; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.notification.dto.ScheduleSaveEvent; import com.example.moim.schedule.dto.*; @@ -36,18 +37,13 @@ public class ScheduleCommandServiceImpl implements ScheduleCommandService { private final MatchApplicationRepository matchApplicationRepository; public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { - /** - * TODO: 친선 매치일 경우 상대방 팀에도 일정 생성되도록 처리해야 하나? - */ + Club club = getClub(scheduleInput.getClubId()); validateClubStaff(getUserClub(club, user)); Schedule schedule = scheduleRepository.save(Schedule.from(club, scheduleInput)); - /** - * TODO: 알림 리팩터링 버전으로 다시 적용해야 함 - */ eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); return new ScheduleOutput(schedule); @@ -55,9 +51,7 @@ public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { @Transactional public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user) { - /** - * TODO: 친선 매치일 경우 상대방 팀에도 일정 수정 반영해야 하나? - */ + Club club = getClub(scheduleUpdateInput.getClubId()); validateClubStaff(getUserClub(club, user)); @@ -74,10 +68,6 @@ public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Lo return new ScheduleOutput(schedule); } - /** - * TODO: 친선 매치라면 같이 삭제해주는 로직 필요 - * @param id - */ public String deleteSchedule(Long id, User user) { Schedule schedule = getSchedule(id); @@ -87,6 +77,9 @@ public String deleteSchedule(Long id, User user) { scheduleRepository.deleteById(id); + // 알림 + eventPublisher.publishEvent(new ScheduleDeleteEvent(schedule, user)); + return "스케줄을 정상적으로 취소하였습니다."; } From 5d3e6f5f964c82fc015faf64445f7d157731e4b7 Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 25 Apr 2025 17:03:22 +0900 Subject: [PATCH 20/33] =?UTF-8?q?refactor(#33):=20=ED=88=AC=ED=91=9C=20voi?= =?UTF-8?q?d=20=EB=A1=9C=20=EC=B2=98=EB=A6=AC=EB=90=9C=20=EC=BB=A8?= =?UTF-8?q?=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EB=AA=A8=EB=93=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EC=9D=91=EB=8B=B5=20=EB=82=98=EA=B0=80=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleVoteController.java | 20 +++++++++----- .../ScheduleVoteControllerDocs.java | 7 ++--- .../vote/service/ScheduleVoteService.java | 7 ++--- .../vote/service/ScheduleVoteServiceImpl.java | 27 +++++++------------ 4 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index dcb8c91..b763ed8 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -1,5 +1,7 @@ package com.example.moim.schedule.vote.controller; +import com.example.moim.global.exception.BaseResponse; +import com.example.moim.global.exception.ResponseCode; import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.schedule.vote.service.ScheduleVoteService; import com.example.moim.user.dto.UserDetailsImpl; @@ -14,17 +16,23 @@ public class ScheduleVoteController implements ScheduleVoteControllerDocs { private final ScheduleVoteService scheduleVoteService; @PatchMapping("/schedules/vote") - public void createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleVoteService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + public BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + String result = scheduleVoteService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + + return BaseResponse.onSuccess(result, ResponseCode.OK); } @PostMapping("/schedules/encourage/{id}") - public void encourageVote(@PathVariable Long id) { - scheduleVoteService.voteEncourage(id); + public BaseResponse encourageVote(@PathVariable Long id) { + String result = scheduleVoteService.voteEncourage(id); + + return BaseResponse.onSuccess(result, ResponseCode.OK); } @PatchMapping("/schedules/close/{id}") - public void closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleVoteService.closeScheduleVote(id, userDetailsImpl.getUser()); + public BaseResponse closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + String result = scheduleVoteService.closeScheduleVote(id, userDetailsImpl.getUser()); + + return BaseResponse.onSuccess(result, ResponseCode.OK); } } diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java index 2a824f8..661e7ad 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -1,5 +1,6 @@ package com.example.moim.schedule.vote.controller; +import com.example.moim.global.exception.BaseResponse; import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.user.dto.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; @@ -9,12 +10,12 @@ public interface ScheduleVoteControllerDocs { @Operation(summary = "일정 참가 투표", description = "참가면 attendance = attend, 참가 취소는 absent") - void createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 독려") - void encourageVote(@PathVariable Long id); + BaseResponse encourageVote(@PathVariable Long id); @Operation(summary = "일정 참가 투표 마감") - void closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + BaseResponse closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java index e53727a..d0b58fe 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -1,11 +1,12 @@ package com.example.moim.schedule.vote.service; +import com.example.moim.global.exception.BaseResponse; import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.user.entity.User; public interface ScheduleVoteService { - void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); - void voteEncourage(Long id); - void closeScheduleVote(Long id, User user); + String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); + String voteEncourage(Long id); + String closeScheduleVote(Long id, User user); } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index eea8b21..5ce4f09 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -4,6 +4,7 @@ import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.ClubRole; +import com.example.moim.global.exception.BaseResponse; import com.example.moim.global.exception.ResponseCode; import com.example.moim.notification.dto.ScheduleEncourageEvent; import com.example.moim.schedule.vote.dto.ScheduleVoteInput; @@ -31,13 +32,8 @@ public class ScheduleVoteServiceImpl implements ScheduleVoteService { private final UserClubRepository userClubRepository; private final ApplicationEventPublisher eventPublisher; - /** - * TODO: void -> 기본 응답 - * @param scheduleVoteInput - * @param user - */ @Transactional - public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { + public String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { Schedule schedule = getSchedule(scheduleVoteInput.getId()); Optional originalScheduleVote = scheduleVoteRepository.findByScheduleAndUser(schedule, user); @@ -57,25 +53,20 @@ public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { // if (scheduleVoteInput.getAttendance().equals("attend")) { // eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); // } + + return schedule.getTitle() + "에 투표를 완료했습니다."; } - /** - * TODO: void -> 기본 응답 - * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) - * @param id - */ - public void voteEncourage(Long id) { + public String voteEncourage(Long id) { Schedule schedule = scheduleRepository.findWithClubById(id); List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); + + return schedule.getTitle() + "의 투표 독려 알림을 보냈습니다."; } - /** - * TODO: void -> 기본 응답, 이름 명확하게 바꾸기 closeScheduleVote 등 - * @param id - */ @Transactional - public void closeScheduleVote(Long id, User user) { + public String closeScheduleVote(Long id, User user) { Schedule schedule = scheduleRepository.findWithClubById(id); UserClub userClub = getUserClub(schedule.getClub(), user); if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { @@ -83,6 +74,8 @@ public void closeScheduleVote(Long id, User user) { } schedule.close(); + + return schedule.getTitle() + "의 투표를 마감했습니다."; } private Schedule getSchedule(Long scheduleId) { From 5dedce26b4b364829183082bc68d2892039fcb33 Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 25 Apr 2025 17:55:42 +0900 Subject: [PATCH 21/33] =?UTF-8?q?refactor(#33):=20=ED=88=AC=ED=91=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EB=93=A4=20=ED=9A=8C=EC=9B=90,=20=EC=9A=B4?= =?UTF-8?q?=EC=98=81=EC=A7=84=20=EA=B6=8C=ED=95=9C=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleVoteController.java | 4 ++-- .../ScheduleVoteControllerDocs.java | 2 +- .../vote/service/ScheduleVoteService.java | 2 +- .../vote/service/ScheduleVoteServiceImpl.java | 22 ++++++++++++++----- .../service/NoticeCommandServiceImplTest.java | 3 +++ .../service/ScheduleVoteServiceImplTest.java | 7 +++++- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index b763ed8..a50da0f 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -23,8 +23,8 @@ public BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput sc } @PostMapping("/schedules/encourage/{id}") - public BaseResponse encourageVote(@PathVariable Long id) { - String result = scheduleVoteService.voteEncourage(id); + public BaseResponse encourageVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + String result = scheduleVoteService.voteEncourage(id, userDetailsImpl.getUser()); return BaseResponse.onSuccess(result, ResponseCode.OK); } diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java index 661e7ad..45d2408 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -13,7 +13,7 @@ public interface ScheduleVoteControllerDocs { BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 독려") - BaseResponse encourageVote(@PathVariable Long id); + BaseResponse encourageVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 마감") BaseResponse closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java index d0b58fe..e94dba8 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -6,7 +6,7 @@ public interface ScheduleVoteService { String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); - String voteEncourage(Long id); + String voteEncourage(Long id, User user); String closeScheduleVote(Long id, User user); } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index 5ce4f09..1245fd9 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -39,6 +39,9 @@ public String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { boolean isAttendance = AttendanceType.fromKoreanName(scheduleVoteInput.getAttendance()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_ATTENDANCE_TYPE)).getIsAttendance(); + // 회원인지 확인 + getUserClub(schedule.getClub(), user); + //투표 처음이면 if (originalScheduleVote.isEmpty()) { scheduleVoteRepository.save(ScheduleVote.createScheduleVote(user, schedule, isAttendance)); @@ -57,8 +60,12 @@ public String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { return schedule.getTitle() + "에 투표를 완료했습니다."; } - public String voteEncourage(Long id) { + public String voteEncourage(Long id, User user) { Schedule schedule = scheduleRepository.findWithClubById(id); + + // 운영진인지 검증 + validateClubStaff(getUserClub(schedule.getClub(), user)); + List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); @@ -68,10 +75,9 @@ public String voteEncourage(Long id) { @Transactional public String closeScheduleVote(Long id, User user) { Schedule schedule = scheduleRepository.findWithClubById(id); - UserClub userClub = getUserClub(schedule.getClub(), user); - if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { - throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); - } + + // 운영진인지 검증 + validateClubStaff(getUserClub(schedule.getClub(), user)); schedule.close(); @@ -85,4 +91,10 @@ private Schedule getSchedule(Long scheduleId) { private UserClub getUserClub(Club club, User user) { return userClubRepository.findByClubAndUser(club, user).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.CLUB_USER_NOT_FOUND)); } + + private void validateClubStaff(UserClub userClub) { + if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { + throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); + } + } } diff --git a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java index d729b3e..43792b3 100644 --- a/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeCommandServiceImplTest.java @@ -16,6 +16,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.multipart.MultipartFile; @@ -36,6 +37,8 @@ class NoticeCommandServiceImplTest { private ClubRepository clubRepository; @Mock private UserClubRepository userClubRepository; + @Mock + private ApplicationEventPublisher eventPublisher; @InjectMocks private NoticeCommandServiceImpl noticeCommandServiceImpl; diff --git a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java index 874011b..23f1db0 100644 --- a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -81,10 +81,12 @@ void voteSchedule_re() { Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, true); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); scheduleVoteService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getIsAttendance()).isFalse(); @@ -100,11 +102,13 @@ void voteSchedule() { Schedule schedule = Schedule.from(club, scheduleInput); ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, false); //when when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); scheduleVoteService.voteSchedule(scheduleVoteInput, user); //then assertThat(scheduleVote.getIsAttendance()).isFalse(); @@ -124,8 +128,9 @@ void voteEncourage() { UserClub userClub = UserClub.createLeaderUserClub(user, club); //when when(scheduleRepository.findWithClubById(any(Long.class))).thenReturn(schedule); + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); - scheduleVoteService.voteEncourage(id); + scheduleVoteService.voteEncourage(id, user); //then verify(scheduleRepository, times(1)).findWithClubById(any(Long.class)); verify(userClubRepository, times(1)).findUserByClub(any(Club.class)); From 7bc893d02e6d648d3d46ef0c0195839bcbafae54 Mon Sep 17 00:00:00 2001 From: 5nam Date: Wed, 30 Apr 2025 09:25:41 +0900 Subject: [PATCH 22/33] =?UTF-8?q?refactor(#33):=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EC=97=90=20=EC=9C=A0=EC=A0=80=EA=B0=80=20=ED=88=AC?= =?UTF-8?q?=ED=91=9C=ED=95=A0=20=EB=95=8C=20=EC=95=8C=EB=A6=BC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/schedule/vote/service/ScheduleVoteServiceImpl.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index 1245fd9..c29a1cb 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -50,12 +50,6 @@ public String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { schedule.reVote(originalScheduleVote.get().getIsAttendance(), isAttendance); originalScheduleVote.get().changeAttendance(isAttendance); } - /** - * TODO: 왜 알림 보내려고 했는지 확인하고 로직 추가하기 - */ -// if (scheduleVoteInput.getAttendance().equals("attend")) { -// eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); -// } return schedule.getTitle() + "에 투표를 완료했습니다."; } From e81d9646c544146eb408ded7520d6da86f1f77e5 Mon Sep 17 00:00:00 2001 From: 5nam Date: Wed, 7 May 2025 17:37:26 +0900 Subject: [PATCH 23/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=84=B8=EB=B6=80=20=EC=A1=B0=ED=9A=8C=EC=97=90=20=EB=8C=93?= =?UTF-8?q?=EA=B8=80=20=EC=A0=95=EB=B3=B4=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/dto/ScheduleClubInfoOutput.java | 10 +++++ .../schedule/dto/ScheduleDetailOutput.java | 38 ++++++++++++++++--- .../moim/schedule/entity/Schedule.java | 2 +- .../repository/ScheduleRepositoryCustom.java | 2 + .../repository/ScheduleRepositoryImpl.java | 10 +++++ .../service/ScheduleQueryServiceImpl.java | 20 +++++----- .../ScheduleRepositoryImplTest.java | 2 +- 7 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/dto/ScheduleClubInfoOutput.java diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleClubInfoOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleClubInfoOutput.java new file mode 100644 index 0000000..1e258e4 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleClubInfoOutput.java @@ -0,0 +1,10 @@ +package com.example.moim.schedule.dto; + +import lombok.Data; + +@Data +public class ScheduleClubInfoOutput { + private String clubName; + private String level; + private String ageRange; +} diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index 798b39a..d3488d1 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -1,5 +1,7 @@ package com.example.moim.schedule.dto; +import com.example.moim.schedule.comment.dto.CommentOutput; +import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.entity.Schedule; import lombok.Data; @@ -23,14 +25,14 @@ public class ScheduleDetailOutput { private int minPeople; private int attend; private int nonAttend; -// List ScheduleUserList; - /** - * TODO: PM 답변에 따라서 상대 팀 정보 추가로 주는지 마는지 반영해서 구현 완료하기 - */ -// private List MatchApplyClubList; + // 댓글 정보 + private List comments; + // 상대 팀 정보 + private Object otherClub; - public ScheduleDetailOutput(Schedule schedule) { + // 정기 운동, 기타 + public ScheduleDetailOutput(Schedule schedule, List comments) { this.id = schedule.getId(); this.title = schedule.getTitle(); this.deadline = schedule.getCreatedDate().plusDays(5).format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm")); @@ -48,6 +50,30 @@ public ScheduleDetailOutput(Schedule schedule) { this.attend = schedule.getAttend(); this.nonAttend = schedule.getNonAttend(); this.viewCount = schedule.getViewCount(); + this.comments = comments; + } + + // 친선 매치, 리그/대회 + public ScheduleDetailOutput(Schedule schedule, List comments, Object clubInfo) { + this.id = schedule.getId(); + this.title = schedule.getTitle(); + this.deadline = schedule.getCreatedDate().plusDays(5).format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm")); + if (LocalDateTime.now().isBefore(schedule.getCreatedDate().plusDays(5))) { + this.isClose = schedule.getIsClose(); + } else { //마감일 전이면 마감 상태 응답, 지났으면 무조건 마감한것으로 응답 + this.isClose = true; + } + this.location = schedule.getLocation(); + this.period = schedule.getStartTime().toLocalDate().toString() + " " + + schedule.getStartTime().toLocalTime().toString() + " ~ " + schedule.getEndTime().toLocalTime().toString(); + this.minPeople = schedule.getMinPeople(); + this.category = schedule.getCategory().getKoreanName(); + this.note = schedule.getNote(); + this.attend = schedule.getAttend(); + this.nonAttend = schedule.getNonAttend(); + this.viewCount = schedule.getViewCount(); + this.comments = comments; + this.otherClub = clubInfo; } // public ScheduleDetailOutput(Schedule schedule, List ScheduleUserOutputList, List MatchApplyClubOutputList) { diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 68d0172..4009562 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -39,7 +39,7 @@ public class Schedule extends BaseEntity { private int viewCount; @OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE) - private List comment = new ArrayList<>(); + private List comments = new ArrayList<>(); public static Schedule from(Club club, ScheduleInput scheduleInput) { Schedule schedule = new Schedule(); diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java index fc92d38..3287564 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java @@ -10,4 +10,6 @@ public interface ScheduleRepositoryCustom { List findByClubAndTime(Club club, LocalDateTime startTime, LocalDateTime endTime, String search, String category); Schedule findWithClubById(Long id); + + Schedule findByIdWithComment(Long id); } diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java index bda58e9..91e977b 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Optional; +import static com.example.moim.schedule.comment.entity.QComment.comment; import static com.example.moim.schedule.entity.QSchedule.schedule; import static org.springframework.util.StringUtils.hasText; @@ -57,4 +58,13 @@ public Schedule findWithClubById(Long id) { .where(schedule.id.eq(id)) .fetchOne(); } + + @Override + public Schedule findByIdWithComment(Long id) { + return queryFactory + .selectFrom(schedule) + .join(schedule.comments, comment).fetchJoin() + .where(schedule.id.eq(id)) + .fetchOne(); + } } diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index 610fc1c..d028c06 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -6,10 +6,12 @@ import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.exception.ResponseCode; import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleOutput; import com.example.moim.schedule.dto.ScheduleSearchMonthInput; import com.example.moim.schedule.entity.Schedule; +import com.example.moim.schedule.entity.ScheduleCategory; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.repository.ScheduleRepository; import com.example.moim.user.entity.User; @@ -33,13 +35,6 @@ public class ScheduleQueryServiceImpl implements ScheduleQueryService { private final UserClubRepository userClubRepository; private final MatchApplicationRepository matchApplicationRepository; - /** - * TODO: 기획에 맞게, 스케줄 조회 기준 바꾸기 - * User 가 가진 스케줄로 조회해야하는거 아닌가..? - * 그럼 이거 구조 아예 다시 바꿔야 할 것 같은데... - * @param scheduleSearchMonthInput - * @return - */ public List findMonthlySchedulesWithFilter(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { Club club = getClub(scheduleSearchMonthInput.getClubId()); @@ -79,14 +74,21 @@ public List findScheduleByDay(ScheduleSearchMonthInput scheduleS */ @Transactional public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { - Schedule schedule = getSchedule(scheduleId); + Schedule schedule = scheduleRepository.findByIdWithComment(scheduleId); // 조회 수 증가 반영 schedule.increaseViewCount(); getUserClub(schedule.getClub(), user); // 권한 확인 - return new ScheduleDetailOutput(schedule); + // 댓글 정보도 함께 포함 + List comments = schedule.getComments().stream().map(CommentOutput::new).toList(); + + if (schedule.getCategory().equals(ScheduleCategory.TOURNAMENT) || schedule.getCategory().equals(ScheduleCategory.FRIENDLY_MATCH)) { + // 상대팀 정보 받아오는 로직 추가 + } + + return new ScheduleDetailOutput(schedule, comments); } private Club getClub(Long clubId) { diff --git a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java index 5abd057..3a4095b 100644 --- a/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java +++ b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java @@ -82,6 +82,6 @@ void findScheduleById() { Schedule result = scheduleRepository.findWithClubById(scheduleId); //then assertThat(result.getTitle()).isEqualTo("운동 매치 스케줄"); - assertThat(result.getComment().get(0).getContents()).isEqualTo("회사 면접이 잡혀있어서 못 갑니다"); + assertThat(result.getComments().get(0).getContents()).isEqualTo("회사 면접이 잡혀있어서 못 갑니다"); } } \ No newline at end of file From 60fe16ded7668f2b212d2357c350b6959ac9db34 Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 9 May 2025 16:15:47 +0900 Subject: [PATCH 24/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=EA=B0=92=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20-?= =?UTF-8?q?=20=EC=9D=BC=EC=A0=95=20=EC=A4=91=20=EC=B9=9C=EC=84=A0=20?= =?UTF-8?q?=EB=A7=A4=EC=B9=98/=EB=8C=80=ED=9A=8C=EB=8A=94=20=EC=83=81?= =?UTF-8?q?=EB=8C=80=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=EB=8F=84=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=9B=EC=95=84=EC=95=BC=20=ED=95=98=EB=AF=80?= =?UTF-8?q?=EB=A1=9C=20save,=20update=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EC=97=90=20=EC=83=81=EB=8C=80=20=ED=8C=80=20=EC=A0=95=EB=B3=B4?= =?UTF-8?q?=20=EB=B0=9B=EB=8A=94=20=EA=B2=83=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EC=99=B8=EB=B6=80=20=EC=83=81=EB=8C=80=20=ED=8C=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=EB=A5=BC=20=EC=A0=80=EC=9E=A5=ED=9E=89=20=EC=9C=84?= =?UTF-8?q?=ED=95=B4=20Schedule=20=EC=97=94=ED=8B=B0=ED=8B=B0=EC=97=90=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=20=EC=BB=AC?= =?UTF-8?q?=EB=9F=BC=EC=9C=BC=EB=A1=9C=20=EC=B6=94=EA=B0=80(@Embeddable=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9.=20=EC=BD=94=EB=93=9C=20=EC=9E=AC=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=84=B1=EC=9D=84=20=EC=9C=84=ED=95=B4=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EB=B6=84=EB=A6=AC)=20-=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=84=B8=EB=B6=80=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?=20=EC=9D=BC=EC=A0=95=20=EC=A2=85=EB=A5=98=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=9D=BC=20=EC=9D=91=EB=8B=B5=20=EA=B0=92=20=EB=8B=A4=EB=A5=B4?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=ED=98=84(=EC=95=84=EC=A7=81=20?= =?UTF-8?q?=EB=A7=A4=EC=B9=98=20=EB=B6=80=EB=B6=84=EC=9D=80=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=AA=BB=ED=95=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/global/exception/ResponseCode.java | 1 + .../com/example/moim/match/entity/Match.java | 2 +- .../match/repository/MatchRepository.java | 8 ++++++ .../schedule/dto/ScheduleDetailOutput.java | 20 ++++++++++++--- .../moim/schedule/dto/ScheduleInput.java | 20 +++++++++++++++ .../schedule/dto/ScheduleUpdateInput.java | 7 +++++- .../schedule/entity/OpponentTeamInfo.java | 17 +++++++++++++ .../moim/schedule/entity/Schedule.java | 24 ++++++++++++++++++ .../schedule/entity/ScheduleCategory.java | 5 ++-- .../service/ScheduleQueryServiceImpl.java | 25 ++++++++++++------- .../service/ScheduleQueryServiceImplTest.java | 6 ++--- 11 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 358f998..6f162c7 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -37,6 +37,7 @@ public enum ResponseCode { MATCH_APPLICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCH4006", "올바른 매치가 아닙니다."), MATCH_TIME_OUT(HttpStatus.BAD_REQUEST, "MATCH4007", "매치가 종료된 후 48시간이 지났습니다."), MATCH_NOT_CONFIRMED(HttpStatus.BAD_REQUEST, "MATCH4008", "확정된 매치가 아닙니다."), + MATCH_SCHEDULE_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCH4009", "매치 일정이 확정되지 않았거나, 존재하지 않습니다."), // MatchUser Error MATCH_USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MATCH4005", "가입된 모임이 없습니다."), diff --git a/src/main/java/com/example/moim/match/entity/Match.java b/src/main/java/com/example/moim/match/entity/Match.java index 1771b7b..a8e1ec6 100644 --- a/src/main/java/com/example/moim/match/entity/Match.java +++ b/src/main/java/com/example/moim/match/entity/Match.java @@ -144,7 +144,7 @@ public ScheduleInput createScheduleFromMatch() { getStartTime(), getEndTime(), getMinParticipants(), - ScheduleCategory.FRIENDLY_MATCH.getKoreanName(), + ScheduleCategory.OFFICIAL_MATCH.getKoreanName(), getNote()); } diff --git a/src/main/java/com/example/moim/match/repository/MatchRepository.java b/src/main/java/com/example/moim/match/repository/MatchRepository.java index c49e267..09a9cc8 100644 --- a/src/main/java/com/example/moim/match/repository/MatchRepository.java +++ b/src/main/java/com/example/moim/match/repository/MatchRepository.java @@ -2,6 +2,7 @@ import com.example.moim.club.entity.Club; import com.example.moim.match.entity.Match; +import com.example.moim.schedule.entity.Schedule; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -10,6 +11,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; @Repository public interface MatchRepository extends JpaRepository, MatchRepositoryCustom { @@ -20,6 +22,12 @@ public interface MatchRepository extends JpaRepository, MatchReposi " or m.awayClub = :club)") List findMatchByClub(@Param("club") Club club); + @Transactional(readOnly = true) + @Query("select m from Match m" + + " where (m.schedule = :schedule" + + " and m.matchStatus = 'CONFIRMED')") + Optional findMatchBySchedule(@Param("schedule") Schedule schedule); + @Transactional(readOnly = true) @Query("select m from Match m" + " where (m.homeClub = :club" + diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index d3488d1..e43aada 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -2,8 +2,11 @@ import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.schedule.comment.entity.Comment; +import com.example.moim.schedule.entity.OpponentTeamInfo; import com.example.moim.schedule.entity.Schedule; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -29,7 +32,7 @@ public class ScheduleDetailOutput { // 댓글 정보 private List comments; // 상대 팀 정보 - private Object otherClub; + private OpponentTeamInfoOutput opponentTeamInfoOutput; // 정기 운동, 기타 public ScheduleDetailOutput(Schedule schedule, List comments) { @@ -54,7 +57,7 @@ public ScheduleDetailOutput(Schedule schedule, List comments) { } // 친선 매치, 리그/대회 - public ScheduleDetailOutput(Schedule schedule, List comments, Object clubInfo) { + public ScheduleDetailOutput(Schedule schedule, List comments, OpponentTeamInfoOutput opponentTeamInfoOutput) { this.id = schedule.getId(); this.title = schedule.getTitle(); this.deadline = schedule.getCreatedDate().plusDays(5).format(DateTimeFormatter.ofPattern("yyyy.MM.dd hh:mm")); @@ -73,7 +76,18 @@ public ScheduleDetailOutput(Schedule schedule, List comments, Obj this.nonAttend = schedule.getNonAttend(); this.viewCount = schedule.getViewCount(); this.comments = comments; - this.otherClub = clubInfo; + this.opponentTeamInfoOutput = opponentTeamInfoOutput; + } + + @Getter + public static class OpponentTeamInfoOutput { + private String teamName; + private String ageRange; + + public OpponentTeamInfoOutput(OpponentTeamInfo opponentTeamInfo) { + this.teamName = opponentTeamInfo.getName(); + this.ageRange = opponentTeamInfo.getAgeRange().getKoreanName(); + } } // public ScheduleDetailOutput(Schedule schedule, List ScheduleUserOutputList, List MatchApplyClubOutputList) { diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java index 5495795..f6e29c7 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java @@ -8,6 +8,7 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.Getter; import java.time.LocalDateTime; @@ -32,6 +33,23 @@ public class ScheduleInput { @NotBlank(message = "일정 카테고리를 입력해 주세요.") private String category; private String note; + // 팀 정보 + private String opponentTeamName; + private String opponentTeamAgeRange; + + @Builder + public ScheduleInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, int minPeople, String category, String note, String opponentTeamName, String opponentTeamAgeRange) { + this.clubId = clubId; + this.title = title; + this.location = location; + this.startTime = startTime; + this.endTime = endTime; + this.minPeople = minPeople; + this.category = category; + this.note = note; + this.opponentTeamName = opponentTeamName; + this.opponentTeamAgeRange = opponentTeamAgeRange; + } @Builder public ScheduleInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, int minPeople, String category, String note) { @@ -44,4 +62,6 @@ public ScheduleInput(Long clubId, String title, String location, LocalDateTime s this.category = category; this.note = note; } + + } diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java index b85ca33..bdd54dc 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java @@ -23,9 +23,12 @@ public class ScheduleUpdateInput { private Integer minPeople; private String category; private String note; + // 팀 정보 + private String opponentTeamName; + private String opponentTeamAgeRange; @Builder - public ScheduleUpdateInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, @NotNull(message = "참여 인원을 입력해주세요.") int minPeople, String category, String note) { + public ScheduleUpdateInput(Long clubId, String title, String location, LocalDateTime startTime, LocalDateTime endTime, @NotNull(message = "참여 인원을 입력해주세요.") int minPeople, String category, String note, String opponentTeamName, String opponentTeamAgeRange) { this.clubId = clubId; this.title = title; this.location = location; @@ -34,5 +37,7 @@ public ScheduleUpdateInput(Long clubId, String title, String location, LocalDate this.minPeople = minPeople; this.category = category; this.note = note; + this.opponentTeamName = opponentTeamName; + this.opponentTeamAgeRange = opponentTeamAgeRange; } } diff --git a/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java new file mode 100644 index 0000000..1e57758 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java @@ -0,0 +1,17 @@ +package com.example.moim.schedule.entity; + +import com.example.moim.global.enums.AgeRange; +import jakarta.persistence.Embeddable; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Setter @Getter +public class OpponentTeamInfo { + private String name; + private AgeRange ageRange; +} diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 4009562..57c4a89 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -1,6 +1,7 @@ package com.example.moim.schedule.entity; import com.example.moim.club.entity.Club; +import com.example.moim.global.enums.AgeRange; import com.example.moim.global.exception.ResponseCode; import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.dto.ScheduleInput; @@ -11,6 +12,7 @@ import jakarta.persistence.*; import lombok.Getter; import lombok.NoArgsConstructor; +import org.springframework.security.core.parameters.P; import java.time.LocalDateTime; import java.util.ArrayList; @@ -37,6 +39,8 @@ public class Schedule extends BaseEntity { private int nonAttend; private Boolean isClose; private int viewCount; + @Embedded + private OpponentTeamInfo opponentTeamInfo; @OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE) private List comments = new ArrayList<>(); @@ -53,6 +57,10 @@ public static Schedule from(Club club, ScheduleInput scheduleInput) { if (scheduleInput.getNote() != null) { schedule.note = scheduleInput.getNote(); } + if (isMatchType(schedule.category) && notNullOpponentTeamObject(scheduleInput.getOpponentTeamName(), scheduleInput.getOpponentTeamAgeRange())) { + AgeRange ageRange = AgeRange.fromKoreanName(scheduleInput.getOpponentTeamAgeRange()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_AGE_RANGE)); + schedule.opponentTeamInfo = new OpponentTeamInfo(scheduleInput.getOpponentTeamName(), ageRange); + } schedule.attend = 0; schedule.nonAttend = 0; schedule.isClose = false; @@ -60,6 +68,14 @@ public static Schedule from(Club club, ScheduleInput scheduleInput) { return schedule; } + private static boolean isMatchType(ScheduleCategory scheduleCategory) { + return scheduleCategory.equals(ScheduleCategory.TOURNAMENT) || scheduleCategory.equals(ScheduleCategory.FRIENDLY_MATCH); + } + + private static boolean notNullOpponentTeamObject(String teamName, String teamAgeRange) { + return teamName != null && teamAgeRange != null; + } + /** * FIXME: 뭔가 지금 이 구조가 이상함. ScheduleVote 에서의 역할이 자꾸 여기서 처리되는듯. 이걸 ScheduleVoteService 에서 Boolean 값으로 넘겨주는게 맞는 듯 * @param attendance @@ -109,6 +125,14 @@ public void update(ScheduleUpdateInput scheduleUpdateInput) { if (scheduleUpdateInput.getNote() != null) { this.note = scheduleUpdateInput.getNote(); } + if (scheduleUpdateInput.getOpponentTeamName() != null) { + this.opponentTeamInfo.setName(scheduleUpdateInput.getOpponentTeamName()); + } + if (scheduleUpdateInput.getOpponentTeamAgeRange() != null) { + this.opponentTeamInfo.setAgeRange( + AgeRange.fromKoreanName(scheduleUpdateInput.getOpponentTeamAgeRange()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_AGE_RANGE)) + ); + } } public void close() { diff --git a/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java index 2751770..732eebe 100644 --- a/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java +++ b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java @@ -6,8 +6,9 @@ public enum ScheduleCategory { REGULAR_TRAINING("정기 운동"), TOURNAMENT("대회"), - FRIENDLY_MATCH("친선 매치"), - ETC("기타"); + FRIENDLY_MATCH("친선 매치"), // 외부 사용자와 매치 + ETC("기타"), + OFFICIAL_MATCH("정식 매치"); // Match 등록에서 하는 매치 private final String koreanName; diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index d028c06..92821d0 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -5,11 +5,14 @@ import com.example.moim.club.repository.ClubRepository; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.match.entity.Match; import com.example.moim.match.repository.MatchApplicationRepository; +import com.example.moim.match.repository.MatchRepository; import com.example.moim.schedule.comment.dto.CommentOutput; import com.example.moim.schedule.dto.ScheduleDetailOutput; import com.example.moim.schedule.dto.ScheduleOutput; import com.example.moim.schedule.dto.ScheduleSearchMonthInput; +import com.example.moim.schedule.entity.OpponentTeamInfo; import com.example.moim.schedule.entity.Schedule; import com.example.moim.schedule.entity.ScheduleCategory; import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; @@ -33,7 +36,7 @@ public class ScheduleQueryServiceImpl implements ScheduleQueryService { private final ScheduleRepository scheduleRepository; private final ClubRepository clubRepository; private final UserClubRepository userClubRepository; - private final MatchApplicationRepository matchApplicationRepository; + private final MatchRepository matchRepository; public List findMonthlySchedulesWithFilter(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { Club club = getClub(scheduleSearchMonthInput.getClubId()); @@ -67,27 +70,31 @@ public List findScheduleByDay(ScheduleSearchMonthInput scheduleS .stream().map(ScheduleOutput::new).collect(Collectors.toList()); } - /** - * FIXME: 세부 일정 페이지 보고 데이터 넘겨주기. 댓글도 포함되어야 함? - * @param scheduleId - * @return - */ @Transactional public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { Schedule schedule = scheduleRepository.findByIdWithComment(scheduleId); + getUserClub(schedule.getClub(), user); // 권한 확인 + // 조회 수 증가 반영 schedule.increaseViewCount(); - getUserClub(schedule.getClub(), user); // 권한 확인 - // 댓글 정보도 함께 포함 List comments = schedule.getComments().stream().map(CommentOutput::new).toList(); + // 친선 매치이거나 대회일 때(외부 사용자) if (schedule.getCategory().equals(ScheduleCategory.TOURNAMENT) || schedule.getCategory().equals(ScheduleCategory.FRIENDLY_MATCH)) { - // 상대팀 정보 받아오는 로직 추가 + return new ScheduleDetailOutput(schedule, comments, new ScheduleDetailOutput.OpponentTeamInfoOutput(schedule.getOpponentTeamInfo())); + } + /** + * TODO: 정식 매치일 때, 전적 부분 공부해서 데이터 담기 + */ + else if (schedule.getCategory().equals(ScheduleCategory.OFFICIAL_MATCH)) { + // 이 경우는 조금 더 고민해보기 + // 리다이렉트나,, 아니면 내가 데이터를 넘겨주는게 맞을듯 } + // 정기 운동, 기타 일정일 때 return new ScheduleDetailOutput(schedule, comments); } diff --git a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java index 18bac08..c713a10 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -153,13 +153,13 @@ void findScheduleDetail() { schedule.setCreatedDate(); schedule.setUpdatedDate(); //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleRepository.findByIdWithComment(any(Long.class))).thenReturn(schedule); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); ScheduleDetailOutput result = scheduleQueryService.findScheduleDetail(1L, user); //then assertThat(result.getTitle()).isEqualTo("title"); assertThat(result.getCategory()).isEqualTo("정기 운동"); - verify(scheduleRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findByIdWithComment(any(Long.class)); } @Test @@ -172,7 +172,7 @@ void findScheduleDetail_non_member() { schedule.setCreatedDate(); schedule.setUpdatedDate(); //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + when(scheduleRepository.findByIdWithComment(any(Long.class))).thenReturn(schedule); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.empty()); //then Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { From 0293d89693b8b74ae754c2a1592e04706f28336c Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 15 May 2025 22:42:01 +0900 Subject: [PATCH 25/33] =?UTF-8?q?refactor(#33):=20JSON=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=ED=83=80=EC=9E=85=20=EC=98=A4=EB=A5=98,=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20par?= =?UTF-8?q?se=20=EC=98=A4=EB=A5=98=20=ED=95=B8=EB=93=A4=EB=9F=AC=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/local.init.sql | 51 ++++--------------- .../moim/global/exception/ResponseCode.java | 2 + .../handler/MasterExceptionHandler.java | 24 +++++++++ 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/deploy/local.init.sql b/deploy/local.init.sql index a1c72cc..efeca71 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -37,45 +37,14 @@ values (default, '2024-12-11', null, 1, 'title2', 'content2'); INSERT INTO notice (id, created_date, updated_date, club_id, title, content) values (default, '2024-12-12', null, 1, 'title3', 'content4'); -INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) -VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'MAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'title nothing', '한양대학교'); -INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) -VALUES (default, null, null, 'SEOUL', 'TWENTIES', 'SCHOOL_GROUP', 'club_password', 'explanation', 'WOMAN', 'introduction', 'SOCCER', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'titlenothing', '서울대학교'); -INSERT INTO club (id, created_date, updated_date, activity_area, age_range, club_category, club_password, explanation, gender, introduction, sports_type, main_uniform_color, match_count, member_count, profile_img_path, schedule_count, sub_uniform_color, title, university) -VALUES (default, null, null, 'GYEONGGI', 'THIRTIES', 'SOCIAL_GROUP', 'club_password', 'explanation', 'UNISEX', 'introduction', 'FUTSAL', 'main_uniform_color', 2, 5, null, 2, 'sub_uniform_color', 'title nothing', '연세대학교'); - -INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) -VALUES (1, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); -INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) -VALUES (2, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); -INSERT INTO CLUB_SEARCH (CLUB_ID, CREATED_DATE, UPDATED_DATE, TITLE_NO_SPACE, INTRO_NO_SPACE, EXP_NO_SPACE, ALL_FIELDS_CONCAT) -VALUES (3, null, null, 'titlenothing', 'introduction', 'explanation', 'titlenothing|introduction|explanation'); - -INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) -VALUES (default, null, null, 'SEOUL', 'birthday', 'email', 'fcm_token', 'WOMAN', 180, null, 'RIGHT', 'ST', 'name', 'password', 'phone', 'refresh_token123456', 'USER', 'LM', 70); -INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) -VALUES (default, null, null, 'SEOUL', 'birthday', 'email', 'fcm_token', 'WOMAN', 180, null, 'LEFT', 'LM', 'name', 'password', 'phone', 'refresh_token12345678', 'USER', 'ST', 70); -INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) -VALUES (default, null, null, 'GYEONGGI', 'birthday', 'email', 'fcm_token', 'MAN', 180, null, 'RIGHT', 'GK', 'name', 'password', 'phone', 'refresh_token123456789', 'USER', 'LM', 70); -INSERT INTO users (user_id, created_date, updated_date, activity_area, birthday, email, fcm_token, gender, height, img_path, main_foot, main_position, name, password, phone, refresh_token, role, sub_position, weight) -VALUES (default, null, null, 'GYEONGGI', 'birthday', 'email', 'fcm_token', 'MAN', 180, null, 'BOTH', 'MANAGER', 'name', 'password', 'phone', 'refresh_token12345678910', 'USER', 'GK', 70); - -INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) -VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 1, 1); ----- 3번 동아리에 가입한 유저들 -INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) -VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 3); -INSERT INTO user_club (id, created_date, updated_date, club_role, join_date, match_count, schedule_count, club_id, user_id) -VALUES (default, null, null, 'STAFF', '2024-12-11', 2, 2, 3, 4); - -- 스케줄 생성(3번 동아리) -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) -values (default, 3, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) -values (default, 3, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) -values (default, 3, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 1); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) -values (default, 3, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 10); -insert into `schedule` (id, club_id, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) -values (default, 3, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 5); \ No newline at end of file +insert into `schedule` (id, club_id, created_date, updated_date, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '2024-03-06 14:30:00', null, '운동 매치 스케줄', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); +insert into `schedule` (id, club_id, created_date, updated_date, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '2024-03-06 14:30:00', null, '운동 매치 스케줄2', '서울시 마포구', '2024-03-11 14:30:00', '2024-03-11 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 0); +insert into `schedule` (id, club_id, created_date, updated_date, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count) +values (default, 3, '2024-03-05 14:30:00', null, '운동 매치 스케줄3', '서울시 마포구', '2024-03-12 14:30:00', '2024-03-12 17:30:00', 10, 'REGULAR_TRAINING', 'note', 10, 12, 0, 1); +insert into `schedule` (id, club_id, created_date, updated_date, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count, opponent_team_name, opponent_team_age_range) +values (default, 3, '2024-04-01 14:30:00', null, '운동 매치 스케줄4', '서울시 마포구', '2024-04-05 14:30:00', '2024-04-05 17:30:00', 10, 'FRIENDLY_MATCH', 'note', 10, 12, 0, 10, 'opponent_team_name', 'TWENTIES'); +insert into `schedule` (id, club_id, created_date, updated_date, title, location, start_time, end_time, min_people, category, note, attend, non_attend, is_close, view_count, opponent_team_name, opponent_team_age_range) +values (default, 3, '2024-02-22 14:30:00', null, '운동 매치 스케줄5', '서울시 마포구', '2024-02-27 14:30:00', '2024-02-27 17:30:00', 10, 'FRIENDLY_MATCH', 'note', 10, 12, 0, 5, 'opponent_team_name', 'THIRTIES'); \ No newline at end of file diff --git a/src/main/java/com/example/moim/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 6f162c7..da78e89 100644 --- a/src/main/java/com/example/moim/global/exception/ResponseCode.java +++ b/src/main/java/com/example/moim/global/exception/ResponseCode.java @@ -21,6 +21,8 @@ public enum ResponseCode { _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON002","권한이 잘못되었습니다"), _METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "COMMON003", "지원하지 않는 Http Method 입니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON004", "금지된 요청입니다."), + _INVALID_FORMAT(HttpStatus.BAD_REQUEST, "COMMON005", "날짜 형식이 잘못되었습니다."), + _MISMATCHED_INPUT(HttpStatus.BAD_REQUEST, "COMMON006", "필드 타입이 일치하지 않습니다."), // Member Error MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), diff --git a/src/main/java/com/example/moim/global/exception/handler/MasterExceptionHandler.java b/src/main/java/com/example/moim/global/exception/handler/MasterExceptionHandler.java index cb85ec0..ad77be3 100644 --- a/src/main/java/com/example/moim/global/exception/handler/MasterExceptionHandler.java +++ b/src/main/java/com/example/moim/global/exception/handler/MasterExceptionHandler.java @@ -3,11 +3,14 @@ import com.example.moim.global.exception.BaseResponse; import com.example.moim.global.exception.GeneralException; import com.example.moim.global.exception.ResponseCode; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -16,6 +19,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.WebRequest; +import java.time.format.DateTimeParseException; import java.util.List; import java.util.stream.Collectors; @@ -33,6 +37,18 @@ public ResponseEntity general(GeneralException e, WebRequest request) { return handleExceptionInternal(e, e.getErrorCode(), request); } + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity formatException(HttpMessageNotReadableException e, WebRequest request) { + Throwable root = getRootCause(e); + if (root instanceof DateTimeParseException) { + return handleExceptionInternal(e, ResponseCode._INVALID_FORMAT, request); + } else if (root instanceof MismatchedInputException) { + return handleExceptionInternal(e, ResponseCode._MISMATCHED_INPUT, request); + + } + return handleExceptionInternal(e, ResponseCode._BAD_REQUEST, request); + } + @ExceptionHandler(Exception.class) public ResponseEntity exception(Exception e, WebRequest request) { e.printStackTrace(); // 클라이언트에게 불필요한 정보를 노출할 수 있으므로 삭제 @@ -87,4 +103,12 @@ private ResponseEntity handleExceptionInternalFalse(Exception e, Respons .body(body); } + private Throwable getRootCause(Throwable ex) { + Throwable result = ex; + while (result.getCause() != null) { + result = result.getCause(); + } + return result; + } + } From d94c97598b80b6a8aa5ed634f472467a630cff2c Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 15 May 2025 22:54:21 +0900 Subject: [PATCH 26/33] =?UTF-8?q?fix(#33):=20join=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=ED=95=98=EB=A9=B4,=20=EB=8C=93=EA=B8=80=EC=9D=B4=20=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=EC=9D=BC=EC=A0=95=EC=9D=80=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EA=B0=80=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D.=20left=20join=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EC=97=AC=20=EB=8C=93=EA=B8=80?= =?UTF-8?q?=EC=9D=B4=20=EC=97=86=EC=96=B4=EB=8F=84,=20=EC=9E=88=EC=96=B4?= =?UTF-8?q?=EB=8F=84=20=EC=A1=B0=ED=9A=8C=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/schedule/repository/ScheduleRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java index 91e977b..4da78b4 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -63,7 +63,7 @@ public Schedule findWithClubById(Long id) { public Schedule findByIdWithComment(Long id) { return queryFactory .selectFrom(schedule) - .join(schedule.comments, comment).fetchJoin() + .leftJoin(schedule.comments, comment).fetchJoin() .where(schedule.id.eq(id)) .fetchOne(); } From a5dfd54b56ca0b747b53ec3789dab0922e49b22c Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 15 May 2025 23:59:03 +0900 Subject: [PATCH 27/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=88=AC=ED=91=9C=20API=20=EC=97=90=EC=84=9C=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20ID=20=EB=B0=9B=EC=95=84=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20PathVariable=20=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleVoteController.java | 18 +++++++++++++----- .../controller/ScheduleVoteControllerDocs.java | 5 ++--- .../schedule/vote/dto/ScheduleVoteInput.java | 6 +++--- .../vote/service/ScheduleVoteService.java | 2 +- .../vote/service/ScheduleVoteServiceImpl.java | 6 +++--- .../service/ScheduleVoteServiceImplTest.java | 8 ++++---- 6 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java index a50da0f..39ddd68 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -2,9 +2,12 @@ import com.example.moim.global.exception.BaseResponse; import com.example.moim.global.exception.ResponseCode; +import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; import com.example.moim.schedule.vote.dto.ScheduleVoteInput; import com.example.moim.schedule.vote.service.ScheduleVoteService; import com.example.moim.user.dto.UserDetailsImpl; +import com.example.moim.user.entity.User; +import com.example.moim.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -15,22 +18,27 @@ public class ScheduleVoteController implements ScheduleVoteControllerDocs { private final ScheduleVoteService scheduleVoteService; - @PatchMapping("/schedules/vote") - public BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - String result = scheduleVoteService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + private final UserRepository userRepository; + + @PostMapping("/schedules/{id}/vote") + public BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + + String result = scheduleVoteService.voteSchedule(scheduleVoteInput, id, userDetailsImpl.getUser()); return BaseResponse.onSuccess(result, ResponseCode.OK); } - @PostMapping("/schedules/encourage/{id}") + @PostMapping("/schedules/{id}/encouragements") public BaseResponse encourageVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + String result = scheduleVoteService.voteEncourage(id, userDetailsImpl.getUser()); return BaseResponse.onSuccess(result, ResponseCode.OK); } - @PatchMapping("/schedules/close/{id}") + @PatchMapping("/schedules/{id}/close-actions") public BaseResponse closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + String result = scheduleVoteService.closeScheduleVote(id, userDetailsImpl.getUser()); return BaseResponse.onSuccess(result, ResponseCode.OK); diff --git a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java index 45d2408..a4fb198 100644 --- a/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -9,13 +9,12 @@ import org.springframework.web.bind.annotation.RequestBody; public interface ScheduleVoteControllerDocs { - @Operation(summary = "일정 참가 투표", description = "참가면 attendance = attend, 참가 취소는 absent") - BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); + @Operation(summary = "일정 참가 투표", description = "참여, 불참") + BaseResponse createScheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @PathVariable("id") Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 독려") BaseResponse encourageVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 참가 투표 마감") BaseResponse closeScheduleVote(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); - } diff --git a/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java b/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java index d55fcb2..0fac756 100644 --- a/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java +++ b/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java @@ -2,15 +2,15 @@ import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@NoArgsConstructor public class ScheduleVoteInput { - private Long id; private String attendance; @Builder - public ScheduleVoteInput(Long id, String attendance) { - this.id = id; + public ScheduleVoteInput(String attendance) { this.attendance = attendance; } } diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java index e94dba8..57a8803 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -5,7 +5,7 @@ import com.example.moim.user.entity.User; public interface ScheduleVoteService { - String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user); + String voteSchedule(ScheduleVoteInput scheduleVoteInput, Long id, User user); String voteEncourage(Long id, User user); String closeScheduleVote(Long id, User user); diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index c29a1cb..d488965 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -33,8 +33,8 @@ public class ScheduleVoteServiceImpl implements ScheduleVoteService { private final ApplicationEventPublisher eventPublisher; @Transactional - public String voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { - Schedule schedule = getSchedule(scheduleVoteInput.getId()); + public String voteSchedule(ScheduleVoteInput scheduleVoteInput, Long id, User user) { + Schedule schedule = getSchedule(id); Optional originalScheduleVote = scheduleVoteRepository.findByScheduleAndUser(schedule, user); boolean isAttendance = AttendanceType.fromKoreanName(scheduleVoteInput.getAttendance()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_ATTENDANCE_TYPE)).getIsAttendance(); @@ -61,7 +61,7 @@ public String voteEncourage(Long id, User user) { validateClubStaff(getUserClub(schedule.getClub(), user)); List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); - eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); +// eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); return schedule.getTitle() + "의 투표 독려 알림을 보냈습니다."; } diff --git a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java index 23f1db0..0c6b765 100644 --- a/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -79,7 +79,7 @@ void voteSchedule_re() { //given Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); + ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().attendance("불참").build(); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, true); @@ -87,7 +87,7 @@ void voteSchedule_re() { when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.of(scheduleVote)); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - scheduleVoteService.voteSchedule(scheduleVoteInput, user); + scheduleVoteService.voteSchedule(scheduleVoteInput, 1L, user); //then assertThat(scheduleVote.getIsAttendance()).isFalse(); verify(scheduleRepository, times(1)).findById(any(Long.class)); @@ -100,7 +100,7 @@ void voteSchedule() { //given Club club = Club.from(clubInput, null); Schedule schedule = Schedule.from(club, scheduleInput); - ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().id(1L).attendance("불참").build(); + ScheduleVoteInput scheduleVoteInput = ScheduleVoteInput.builder().attendance("불참").build(); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); ScheduleVote scheduleVote = ScheduleVote.createScheduleVote(user, schedule, false); @@ -109,7 +109,7 @@ void voteSchedule() { when(scheduleVoteRepository.findByScheduleAndUser(any(Schedule.class), any(User.class))).thenReturn(Optional.empty()); when(scheduleVoteRepository.save(any(ScheduleVote.class))).thenReturn(scheduleVote); when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - scheduleVoteService.voteSchedule(scheduleVoteInput, user); + scheduleVoteService.voteSchedule(scheduleVoteInput, 1L, user); //then assertThat(scheduleVote.getIsAttendance()).isFalse(); verify(scheduleRepository, times(1)).findById(any(Long.class)); From 38f1291b0ed200ff19dc3786042e4c33d4e26381 Mon Sep 17 00:00:00 2001 From: 5nam Date: Thu, 15 May 2025 23:59:30 +0900 Subject: [PATCH 28/33] =?UTF-8?q?chore(#33):=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ScheduleCommentController.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java index 605628b..508c516 100644 --- a/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java @@ -6,8 +6,6 @@ import com.example.moim.schedule.comment.service.ScheduleCommentCommandService; import com.example.moim.schedule.comment.dto.CommentInput; import com.example.moim.user.dto.UserDetailsImpl; -import com.example.moim.user.entity.User; -import com.example.moim.user.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -22,18 +20,8 @@ public class ScheduleCommentController { private final ScheduleCommentCommandService scheduleCommentCommandService; - /** - * FIXME: 컨트롤러 수동테스트를 위해 필요한 코드, 추후에 지울 것. - */ - private final UserRepository userRepository; - @PostMapping("/schedules/{id}/comments") public BaseResponse scheduleComment(@RequestBody @Valid CommentInput commentInput, @PathVariable("id") Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - /** - * FIXME: 컨트롤러 수동테스트를 위해 필요한 코드, 추후에 지울 것. - */ -// public BaseResponse scheduleComment(@RequestBody @Valid CommentInput commentInput, @PathVariable("id") Long scheduleId) { -// User user = userRepository.findById(3L).get(); CommentOutput commentOutput = scheduleCommentCommandService.saveComment(commentInput, scheduleId, userDetailsImpl.getUser()); return BaseResponse.onSuccess(commentOutput, ResponseCode.OK); } From 3ffab85182c7cd99fdc894a205e563b1bd805f2a Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 16 May 2025 00:07:35 +0900 Subject: [PATCH 29/33] =?UTF-8?q?refactor(#33):=20Enumerated=20=EC=96=B4?= =?UTF-8?q?=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=20=ED=8C=80=20=EC=97=B0=EB=A0=B9=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=EB=A1=9C=20=EC=A0=80=EC=9E=A5=EB=90=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/moim/schedule/dto/ScheduleDetailOutput.java | 8 ++++---- .../example/moim/schedule/dto/ScheduleUpdateInput.java | 4 ++-- .../example/moim/schedule/entity/OpponentTeamInfo.java | 7 +++++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java index e43aada..3bfb67d 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -81,12 +81,12 @@ public ScheduleDetailOutput(Schedule schedule, List comments, Opp @Getter public static class OpponentTeamInfoOutput { - private String teamName; - private String ageRange; + private final String teamName; + private final String ageRange; public OpponentTeamInfoOutput(OpponentTeamInfo opponentTeamInfo) { - this.teamName = opponentTeamInfo.getName(); - this.ageRange = opponentTeamInfo.getAgeRange().getKoreanName(); + this.teamName = opponentTeamInfo.getOpponentTeamName(); + this.ageRange = opponentTeamInfo.getOpponentTeamAgeRange().getKoreanName(); } } diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java index bdd54dc..a2c91b7 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleUpdateInput.java @@ -15,9 +15,9 @@ public class ScheduleUpdateInput { private Long clubId; private String title; private String location; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul") private LocalDateTime startTime; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul") private LocalDateTime endTime; @Min(value = 1, message = "참여 인원은 1명 이상이어야 합니다") private Integer minPeople; diff --git a/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java index 1e57758..be05034 100644 --- a/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java +++ b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java @@ -2,6 +2,8 @@ import com.example.moim.global.enums.AgeRange; import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,6 +14,7 @@ @AllArgsConstructor @Setter @Getter public class OpponentTeamInfo { - private String name; - private AgeRange ageRange; + private String opponentTeamName; + @Enumerated(value = EnumType.STRING) + private AgeRange opponentTeamAgeRange; } From ce5a7575dc775332e28840ff3935b43880e58e22 Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 16 May 2025 00:10:04 +0900 Subject: [PATCH 30/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20Input=20DTO=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/schedule/dto/ScheduleInput.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java index f6e29c7..3e21a1f 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleInput.java @@ -5,31 +5,28 @@ import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.Getter; +import lombok.*; import java.time.LocalDateTime; @Data +@NoArgsConstructor public class ScheduleInput { - @NotBlank(message = "클럽 아이디를 입력해주세요.") + @NotNull(message = "클럽 아이디를 입력해주세요.") private Long clubId; @NotBlank(message = "일정 제목을 입력해주세요.") private String title; @NotBlank(message = "일정 장소를 입력해주세요.") private String location; - @Schema(pattern = "yyyy-MM-dd HH:mm", description = "yyyy-MM-dd HH:mm") - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotBlank(message = "일정 시작 시간을 입력해주세요.") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul") + @NotNull(message = "일정 시작 시간을 입력해주세요.") private LocalDateTime startTime; - @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") - @NotBlank(message = "일정 종료 시간을 입력해주세요.") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul") + @NotNull(message = "일정 종료 시간을 입력해주세요.") private LocalDateTime endTime; @Min(value = 1, message = "참여 인원은 1명 이상이어야 합니다") - @NotBlank(message = "최소 참여 인원을 입력해주세요.") - private int minPeople;//참여인원수 + @NotNull(message = "최소 참여 인원을 입력해주세요.") + private Integer minPeople;//참여인원수 @NotBlank(message = "일정 카테고리를 입력해 주세요.") private String category; private String note; From 34b5ba79a612288fa4deebe5c0fd268e77eb5fbc Mon Sep 17 00:00:00 2001 From: 5nam Date: Fri, 16 May 2025 00:12:15 +0900 Subject: [PATCH 31/33] =?UTF-8?q?refactor(#33):=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EC=8B=9C,=20=EC=83=81?= =?UTF-8?q?=EB=8C=80=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=9D=B4=EC=A0=84=20=ED=8C=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=EA=B0=80=20=EC=9E=88=EC=9D=84=EB=95=8C?= =?UTF-8?q?=EC=99=80=20=EC=97=86=EC=9D=84=20=EB=95=8C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC=20-=20=EC=9E=88=EC=9D=84=20=EA=B2=BD=EC=9A=B0,=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EA=B0=80=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=ED=95=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EB=A7=8C=20=EB=B3=80=EA=B2=BD=EB=90=98=EB=8F=84=EB=A1=9D=20set?= =?UTF-8?q?ter=20=EC=82=AC=EC=9A=A9=20-=20=EC=97=86=EC=9D=84=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0,=20=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=83=81=EB=8C=80?= =?UTF-8?q?=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=20=EA=B0=9D=EC=B2=B4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=98=EC=97=AC=20=ED=95=A0=EB=8B=B9=20-?= =?UTF-8?q?=20=EC=97=86=EC=9D=84=20=EA=B2=BD=EC=9A=B0,=20setter=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=EB=90=98=EB=A9=B4=20NullPointerException=20?= =?UTF-8?q?=EB=B0=9C=EC=83=9D=ED=95=98=EC=97=AC=20=EC=9D=B4=EB=A0=87?= =?UTF-8?q?=EA=B2=8C=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/moim/schedule/entity/Schedule.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/entity/Schedule.java b/src/main/java/com/example/moim/schedule/entity/Schedule.java index 57c4a89..4a22003 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -125,11 +125,21 @@ public void update(ScheduleUpdateInput scheduleUpdateInput) { if (scheduleUpdateInput.getNote() != null) { this.note = scheduleUpdateInput.getNote(); } - if (scheduleUpdateInput.getOpponentTeamName() != null) { - this.opponentTeamInfo.setName(scheduleUpdateInput.getOpponentTeamName()); + // 이전에 팀 정보가 있었으면 + if (this.opponentTeamInfo != null) { + if (scheduleUpdateInput.getOpponentTeamName() != null) { + this.opponentTeamInfo.setOpponentTeamName(scheduleUpdateInput.getOpponentTeamName()); + } + if (scheduleUpdateInput.getOpponentTeamAgeRange() != null) { + this.opponentTeamInfo.setOpponentTeamAgeRange( + AgeRange.fromKoreanName(scheduleUpdateInput.getOpponentTeamAgeRange()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_AGE_RANGE)) + ); + } } - if (scheduleUpdateInput.getOpponentTeamAgeRange() != null) { - this.opponentTeamInfo.setAgeRange( + // 이전에 팀 정보가 없었으면 + if (scheduleUpdateInput.getOpponentTeamName() != null && scheduleUpdateInput.getOpponentTeamAgeRange() != null) { + this.opponentTeamInfo = new OpponentTeamInfo( + scheduleUpdateInput.getOpponentTeamName(), AgeRange.fromKoreanName(scheduleUpdateInput.getOpponentTeamAgeRange()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_AGE_RANGE)) ); } From d2bd24290bdca83b9746799f3838c1d073323a40 Mon Sep 17 00:00:00 2001 From: 5nam Date: Mon, 26 May 2025 23:16:41 +0900 Subject: [PATCH 32/33] =?UTF-8?q?docs(#33):=20=EC=8A=A4=EC=BC=80=EC=A4=84?= =?UTF-8?q?=20=EC=A0=95=EC=8B=9D=20=EB=A7=A4=EC=B9=98=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=20=EB=B6=80=EB=B6=84=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moim/schedule/service/ScheduleQueryServiceImpl.java | 5 ++--- .../moim/schedule/vote/service/ScheduleVoteServiceImpl.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java index 92821d0..93c0377 100644 --- a/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -87,11 +87,10 @@ public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { return new ScheduleDetailOutput(schedule, comments, new ScheduleDetailOutput.OpponentTeamInfoOutput(schedule.getOpponentTeamInfo())); } /** - * TODO: 정식 매치일 때, 전적 부분 공부해서 데이터 담기 + * 전적 생기면 구현 예정 */ else if (schedule.getCategory().equals(ScheduleCategory.OFFICIAL_MATCH)) { - // 이 경우는 조금 더 고민해보기 - // 리다이렉트나,, 아니면 내가 데이터를 넘겨주는게 맞을듯 + } // 정기 운동, 기타 일정일 때 diff --git a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java index d488965..7606250 100644 --- a/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -61,7 +61,7 @@ public String voteEncourage(Long id, User user) { validateClubStaff(getUserClub(schedule.getClub(), user)); List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); -// eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); + eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); return schedule.getTitle() + "의 투표 독려 알림을 보냈습니다."; } From 4b606900d72ccfefdd3b44a48ccff07500099551 Mon Sep 17 00:00:00 2001 From: 5nam Date: Wed, 28 May 2025 11:05:37 +0900 Subject: [PATCH 33/33] =?UTF-8?q?fix(#33):=20merge=20=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=9E=98=EB=AA=BB=EB=90=9C=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EB=AA=85=20=EC=82=AC=EC=9A=A9=EA=B3=BC=20=EC=9E=98=EB=AA=BB?= =?UTF-8?q?=EB=90=9C=20=EB=A1=9C=EC=A7=81=EC=9C=BC=EB=A1=9C=20=EC=9D=B8?= =?UTF-8?q?=ED=95=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/moim/club/entity/Club.java | 13 +++++-------- .../moim/global/util/file/model/FileInfo.java | 2 +- .../club/service/ClubCommandServiceImplTest.java | 4 ++-- .../ScheduleCommentCommandServiceImplTest.java | 9 +++++++-- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/example/moim/club/entity/Club.java b/src/main/java/com/example/moim/club/entity/Club.java index a0d1b5e..cd3d37f 100644 --- a/src/main/java/com/example/moim/club/entity/Club.java +++ b/src/main/java/com/example/moim/club/entity/Club.java @@ -70,15 +70,12 @@ public static Club from(ClubInput clubInput, FileInfo fileInfo) { club.title = clubInput.getTitle(); club.explanation = clubInput.getExplanation(); club.introduction = clubInput.getIntroduction(); - club.clubCategory = ClubCategory.fromKoreanName(clubInput.getClubCategory()).get(); + club.clubCategory = ClubCategory.fromKoreanName(clubInput.getClubCategory()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.INVALID_CLUB_CATEGORY)); club.university = clubInput.getUniversity(); - club.gender = Gender.fromKoreanName(clubInput.getGender()).get(); - club.activityArea = ActivityArea.fromKoreanName(clubInput.getActivityArea()).get(); - club.sportsType = SportsType.fromKoreanName(clubInput.getSportsType()).get(); - club.ageRange = AgeRange.fromKoreanName(clubInput.getAgeRange()).get(); - if (!clubInput.getClubPassword().equals(clubInput.getClubCheckPassword())) { - throw new ClubControllerAdvice(ResponseCode.CLUB_CHECK_PASSWORD_INCORRECT); - } + club.gender = Gender.fromKoreanName(clubInput.getGender()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.INVALID_GENDER)); + club.activityArea = ActivityArea.fromKoreanName(clubInput.getActivityArea()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.INVALID_ACTIVITY_AREA)); + club.sportsType = SportsType.fromKoreanName(clubInput.getSportsType()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.INVALID_SPORTS_TYPE)); + club.ageRange = AgeRange.fromKoreanName(clubInput.getAgeRange()).orElseThrow(() -> new ClubControllerAdvice(ResponseCode.INVALID_AGE_RANGE)); club.clubPassword = clubInput.getClubPassword(); if (fileInfo != null) { club.imgUrl = fileInfo.getFileUrl(); diff --git a/src/main/java/com/example/moim/global/util/file/model/FileInfo.java b/src/main/java/com/example/moim/global/util/file/model/FileInfo.java index 93a1f07..cbf34ac 100644 --- a/src/main/java/com/example/moim/global/util/file/model/FileInfo.java +++ b/src/main/java/com/example/moim/global/util/file/model/FileInfo.java @@ -11,7 +11,7 @@ public class FileInfo { private String fileUrl; @Builder - public FileInfo(String originalFileName, String storedFileName, String fileUrl, String contentType, long size) { + public FileInfo(String originalFileName, String storedFileName, String fileUrl) { this.originalFileName = originalFileName; this.storedFileName = storedFileName; this.fileUrl = fileUrl; diff --git a/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java b/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java index eb02838..b6ad7a0 100644 --- a/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/ClubCommandServiceImplTest.java @@ -250,7 +250,7 @@ void updateClubUser_exception_wrong_permission() { @DisplayName("운영진은 사용자를 모임에서 내보낼 수 있다") void deleteClubUser() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); //when when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); @@ -271,7 +271,7 @@ void deleteClubUser() { @DisplayName("운영진이 아니면 모임에 속한 사용자를 내보내려 할 때 예외가 발생한다") void delete_ClubUser_exception_wrong_permission() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); //when //then diff --git a/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java index d2f8c32..dbd4a51 100644 --- a/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java +++ b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java @@ -5,6 +5,7 @@ import com.example.moim.club.entity.UserClub; import com.example.moim.club.repository.UserClubRepository; import com.example.moim.global.enums.*; +import com.example.moim.global.util.file.model.FileInfo; import com.example.moim.schedule.comment.entity.Comment; import com.example.moim.schedule.comment.repository.CommentRepository; import com.example.moim.schedule.comment.dto.CommentInput; @@ -49,6 +50,7 @@ class ScheduleCommentCommandServiceImplTest { private SignupInput signupInput; private ClubInput clubInput; private CommentInput commentInput; + private FileInfo fileInfo; @BeforeEach void init() { @@ -69,13 +71,16 @@ void init() { // commentInput 생성 this.commentInput = CommentInput.builder().contents("일정이 있어 참가 못합니다").build(); + + // fileInfo 생성 + this.fileInfo = FileInfo.builder().fileUrl("fileUrl").storedFileName("storedFileName").originalFileName("originalFileName").build(); } @Test @DisplayName("회원은 일정에 댓글을 달 수 있다.") void saveComment() { // given - Club club = Club.from(clubInput, "/image"); + Club club = Club.from(clubInput, fileInfo); User user = User.createUser(signupInput); UserClub userClub = UserClub.createLeaderUserClub(user, club); Schedule schedule = Schedule.from(club, scheduleInput); @@ -99,7 +104,7 @@ void saveComment() { @DisplayName("비회원은 일정에 댓글을 달 수 없다.") void saveComment_non_member() { // given - Club club = Club.from(clubInput, "/image"); + Club club = Club.from(clubInput, fileInfo); User user = User.createUser(signupInput); Schedule schedule = Schedule.from(club, scheduleInput); Comment comment = Comment.createComment(user, schedule, commentInput.getContents());