diff --git a/deploy/local.init.sql b/deploy/local.init.sql index a467f24..44d15b9 100644 --- a/deploy/local.init.sql +++ b/deploy/local.init.sql @@ -35,4 +35,16 @@ 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'); \ No newline at end of file +values (default, '2024-12-12', null, 1, 'title3', 'content4'); + +-- 스케줄 생성(3번 동아리) +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/club/entity/Club.java b/src/main/java/com/example/moim/club/entity/Club.java index f92fa0a..cd3d37f 100644 --- a/src/main/java/com/example/moim/club/entity/Club.java +++ b/src/main/java/com/example/moim/club/entity/Club.java @@ -65,20 +65,17 @@ public class Club extends BaseEntity { /** * TODO : university 는 없을 수도 있으므로, null 일 경우를 처리해주기 */ - public static Club createClub(ClubInput clubInput, FileInfo fileInfo) { + public static Club from(ClubInput clubInput, FileInfo fileInfo) { Club club = new Club(); 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(); @@ -91,7 +88,7 @@ public static Club createClub(ClubInput clubInput, FileInfo fileInfo) { return club; } - public Club updateClubSearch(ClubSearch clubSearch) { + public Club updateSearch(ClubSearch clubSearch) { this.clubSearch = clubSearch; return this; } @@ -104,7 +101,7 @@ public void plusMemberCount() { memberCount++; } - public void updateClub(ClubUpdateInput clubUpdateInput, FileInfo fileInfo) { + public void update(ClubUpdateInput clubUpdateInput, FileInfo fileInfo) { if (StringUtils.hasText(clubUpdateInput.getTitle())) { this.title = clubUpdateInput.getTitle(); } @@ -139,7 +136,7 @@ public void updateClub(ClubUpdateInput clubUpdateInput, FileInfo fileInfo) { } } - 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 080332b..0d3f050 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); @@ -67,8 +67,8 @@ public ClubUpdateOutput updateClub(User user, ClubUpdateInput clubUpdateInput, L if (clubUpdateInput.getProfileImg() != null) { fileService.remove(club.getStoredImgName()); } + club.update(clubUpdateInput, fileService.upload(clubUpdateInput.getProfileImg(), "/club-profile")); - club.updateClub(clubUpdateInput, fileService.upload(clubUpdateInput.getProfileImg(), "/club-profile")); // 검색 정보 동기화를 위한 처리 club.getClubSearch().updateFrom(club); List userList = userClubRepository.findAllByClub(club).stream().map(UserClubOutput::new).toList(); @@ -87,9 +87,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); } @@ -146,7 +144,7 @@ public String clubPasswordUpdate(User user, ClubPswdUpdateInput clubPswdUpdateIn throw new ClubControllerAdvice(ResponseCode.CLUB_CHECK_PASSWORD_INCORRECT); } - club.updateClubPassword(clubPswdUpdateInput.getNewPassword()); + club.updatePassword(clubPswdUpdateInput.getNewPassword()); return club.getTitle() + "의 비밀번호를 변경하였습니다."; } @@ -178,7 +176,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/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java index b42a3f5..e7ce427 100644 --- a/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java +++ b/src/main/java/com/example/moim/club/service/NoticeCommandServiceImpl.java @@ -12,10 +12,15 @@ import com.example.moim.global.enums.ClubRole; import com.example.moim.global.exception.ResponseCode; import com.example.moim.user.entity.User; +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 @RequiredArgsConstructor @@ -23,7 +28,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 +43,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/config/security/SecurityConfig.java b/src/main/java/com/example/moim/config/security/SecurityConfig.java index 3fed711..54c7a6a 100644 --- a/src/main/java/com/example/moim/config/security/SecurityConfig.java +++ b/src/main/java/com/example/moim/config/security/SecurityConfig.java @@ -69,7 +69,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { * FIXME: 이건 API 수동 테스트할때만 필요한 것 */ httpSecurity.csrf(csrf -> csrf - .ignoringRequestMatchers("/club/**") + .ignoringRequestMatchers("/clubs/**", "/schedules/**", "/notices/**") ); httpSecurity.csrf(csrf -> csrf.ignoringRequestMatchers("/notice/**")); @@ -78,7 +78,7 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { //http basic 인증 방식 disable httpSecurity.httpBasic(AbstractHttpConfigurer::disable); /** - * FIXME: 이건 API 수동 테스트할때만 필요한 것 + * FIXME: 이건 API 수동 테스트할때만 필요한 것 : 주석 제거하기 */ //경로별 인가 작업 // httpSecurity.authorizeHttpRequests((auth) -> auth @@ -92,7 +92,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/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/global/exception/ResponseCode.java b/src/main/java/com/example/moim/global/exception/ResponseCode.java index 86b9f9d..da78e89 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", "서버 에러, 관리자에게 문의 바랍니다."), @@ -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", "사용자가 없습니다."), @@ -37,6 +39,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", "가입된 모임이 없습니다."), @@ -60,6 +63,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", "이미지 파일만 업로드할 수 있습니다."), @@ -73,7 +79,9 @@ 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", "유효하지 않은 일정 종류입니다."), + INVALID_ATTENDANCE_TYPE(HttpStatus.BAD_REQUEST, "ENUM4010", "유효하지 않은 투표입니다.(참석/불참)"); private final HttpStatus httpStatus; private final String code; 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; + } + } 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/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 e762186..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,11 +1,11 @@ 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.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; @@ -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(), - scheduleService.findSchedule(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/match/entity/Match.java b/src/main/java/com/example/moim/match/entity/Match.java index ab81b01..a8e1ec6 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.OFFICIAL_MATCH.getKoreanName(), getNote()); } 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/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/match/service/MatchService.java b/src/main/java/com/example/moim/match/service/MatchService.java index 754e7b0..84cf6ed 100644 --- a/src/main/java/com/example/moim/match/service/MatchService.java +++ b/src/main/java/com/example/moim/match/service/MatchService.java @@ -1,20 +1,19 @@ 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; 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.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; @@ -46,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; @@ -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); @@ -135,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())); } @@ -152,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())); } @@ -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); @@ -304,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/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/comment/controller/ScheduleCommentController.java b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java new file mode 100644 index 0000000..508c516 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentController.java @@ -0,0 +1,29 @@ +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.comment.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.PathVariable; +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 BaseResponse scheduleComment(@RequestBody @Valid CommentInput commentInput, @PathVariable("id") Long scheduleId, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + 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 new file mode 100644 index 0000000..65fb2c9 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/controller/ScheduleCommentControllerDocs.java @@ -0,0 +1,15 @@ +package com.example.moim.schedule.comment.controller; + +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; +import jakarta.validation.Valid; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.RequestBody; + +@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/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 61% 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 c24ea0f..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,6 +1,6 @@ -package com.example.moim.schedule.dto; +package com.example.moim.schedule.comment.dto; -import com.example.moim.schedule.entity.Comment; +import com.example.moim.schedule.comment.entity.Comment; import lombok.Data; @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/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/comment/service/ScheduleCommentCommandService.java b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java new file mode 100644 index 0000000..8a470dd --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandService.java @@ -0,0 +1,9 @@ +package com.example.moim.schedule.comment.service; + +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 { + 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 new file mode 100644 index 0000000..08336a5 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImpl.java @@ -0,0 +1,44 @@ +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.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; +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; + private final UserClubRepository userClubRepository; + + 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/controller/ScheduleController.java b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java index 04dbefb..62c979d 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleController.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleController.java @@ -1,13 +1,12 @@ package com.example.moim.schedule.controller; -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.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.repository.UserRepository; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.MediaType; @@ -18,56 +17,46 @@ @RestController @RequiredArgsConstructor -public class ScheduleController implements ScheduleControllerDocs{ - private final ScheduleService scheduleService; +public class ScheduleController implements ScheduleControllerDocs { + private final ScheduleCommandService scheduleCommandService; + private final ScheduleQueryService scheduleQueryService; - @PostMapping(value = "/schedule") - public ScheduleOutput scheduleSave(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); - } - - @PatchMapping("/schedule") - public ScheduleOutput scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - return scheduleService.updateSchedule(scheduleUpdateInput, userDetailsImpl.getUser()); - } + private final UserRepository userRepository; - @GetMapping(value = "/schedule", produces = MediaType.APPLICATION_JSON_VALUE) - public List scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleService.findSchedule(scheduleSearchInput); + @PostMapping(value = "/schedules") + public BaseResponse createSchedule(@RequestBody @Valid ScheduleInput scheduleInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + ScheduleOutput scheduleOutput = scheduleCommandService.saveSchedule(scheduleInput, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleOutput, ResponseCode.OK); } - @GetMapping(value = "/schedule/day", produces = MediaType.APPLICATION_JSON_VALUE) - public List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput) { - return scheduleService.findDaySchedule(scheduleSearchInput); + @PatchMapping("/schedules/{id}") + 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("/schedule/{id}") - public ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id) { - return scheduleService.findScheduleDetail(id); + @GetMapping(value = "/schedules", produces = MediaType.APPLICATION_JSON_VALUE) + public BaseResponse> searchScheduleListByMonth(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + List scheduleList = scheduleQueryService.findMonthlySchedulesWithFilter(scheduleSearchMonthInput, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } - @DeleteMapping("/schedule/{id}") - public void scheduleDelete(@PathVariable Long id) { - scheduleService.deleteSchedule(id); + @GetMapping(value = "/schedules/day", produces = MediaType.APPLICATION_JSON_VALUE) + public BaseResponse> searchScheduleListByDay(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + List scheduleList = scheduleQueryService.findScheduleByDay(scheduleSearchMonthInput, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleList, ResponseCode.OK); } - @PatchMapping("/schedule/vote") - public void scheduleVote(@RequestBody ScheduleVoteInput scheduleVoteInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleService.voteSchedule(scheduleVoteInput, userDetailsImpl.getUser()); + @GetMapping("/schedules/{id}") + public BaseResponse getScheduleDetail(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { + ScheduleDetailOutput scheduleDetail = scheduleQueryService.findScheduleDetail(id, userDetailsImpl.getUser()); + return BaseResponse.onSuccess(scheduleDetail, ResponseCode.OK); } - @PostMapping("/schedule/encourage/{id}") - public void voteEncourage(@PathVariable Long id) { - scheduleService.voteEncourage(id); + @DeleteMapping("/schedules/{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); } - - @PatchMapping("/schedule/close/{id}") - public void scheduleClose(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl) { - scheduleService.closeSchedule(id, 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..4b8efa7 100644 --- a/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java +++ b/src/main/java/com/example/moim/schedule/controller/ScheduleControllerDocs.java @@ -1,11 +1,11 @@ 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; -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.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,29 +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 scheduleSave(@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 scheduleUpdate(@RequestBody ScheduleUpdateInput scheduleUpdateInput, @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 scheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + @Operation(summary = "한달 일정 조회", description = "카테고리는 친선 매치/정기 운동/대회/기타 중에 하나여야 합니다(띄어쓰기까지 포함)") + BaseResponse> searchScheduleListByMonth(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "하루 일정 조회", description = "쿼리파라미터 예시: /schedule/day?date=20240910&clubId=6&search=친선 경기&category=친선 경기") - List dayScheduleFind(@ModelAttribute ScheduleSearchInput scheduleSearchInput); + BaseResponse> searchScheduleListByDay(@ModelAttribute ScheduleSearchMonthInput scheduleSearchMonthInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 세부 조회", description = "참가면 attendance = attend, 참가 취소는 absent, 투표 안하면 notVote") - ScheduleDetailOutput scheduleDetailFind(@PathVariable Long id); + BaseResponse getScheduleDetail(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); @Operation(summary = "일정 삭제") - void scheduleDelete(@PathVariable Long id); + BaseResponse deleteSchedule(@PathVariable Long id, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); +// BaseResponse deleteSchedule(@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); - -// @Operation(summary = "일정 댓글", description = "id는 일정 id") -// void scheduleComment(@RequestBody CommentInput commentInput, @AuthenticationPrincipal UserDetailsImpl userDetailsImpl); } 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/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 29d0076..3bfb67d 100644 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleDetailOutput.java @@ -1,8 +1,12 @@ 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.OpponentTeamInfo; import com.example.moim.schedule.entity.Schedule; -import com.example.moim.match.dto.MatchApplyClubOutput; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -13,35 +17,77 @@ public class ScheduleDetailOutput { private Long id; private String title; private String deadline; - private Boolean isClose; - private String location; + private int viewCount; + private String category; + private Boolean isClose; // 투표 마감 + // 일정 정보 + 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; - List MatchApplyClubList; - public ScheduleDetailOutput(Schedule schedule, List MatchApplyClubOutputList) { + // 댓글 정보 + private List comments; + // 상대 팀 정보 + private OpponentTeamInfoOutput opponentTeamInfoOutput; + + // 정기 운동, 기타 + 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")); if (LocalDateTime.now().isBefore(schedule.getCreatedDate().plusDays(5))) { this.isClose = schedule.getIsClose(); - } else {//마감일 전이면 마감 상태 응답, 지났으면 무조건 마감한것으로 응답 + } 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(); + this.category = schedule.getCategory().getKoreanName(); this.note = schedule.getNote(); this.attend = schedule.getAttend(); this.nonAttend = schedule.getNonAttend(); - this.MatchApplyClubList = MatchApplyClubOutputList; + this.viewCount = schedule.getViewCount(); + this.comments = comments; + } + + // 친선 매치, 리그/대회 + 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")); + 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.opponentTeamInfoOutput = opponentTeamInfoOutput; + } + + @Getter + public static class OpponentTeamInfoOutput { + private final String teamName; + private final String ageRange; + + public OpponentTeamInfoOutput(OpponentTeamInfo opponentTeamInfo) { + this.teamName = opponentTeamInfo.getOpponentTeamName(); + this.ageRange = opponentTeamInfo.getOpponentTeamAgeRange().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 3cd2feb..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,35 +5,37 @@ 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.*; import java.time.LocalDateTime; @Data +@NoArgsConstructor public class ScheduleInput { + @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") + @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") + @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명 이상이어야 합니다") @NotNull(message = "최소 참여 인원을 입력해주세요.") - private int minPeople;//참여인원수 + private Integer minPeople;//참여인원수 @NotBlank(message = "일정 카테고리를 입력해 주세요.") private String category; private String note; + // 팀 정보 + private String opponentTeamName; + private String opponentTeamAgeRange; @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, String opponentTeamName, String opponentTeamAgeRange) { this.clubId = clubId; this.title = title; this.location = location; @@ -42,5 +44,21 @@ public ScheduleInput(Long clubId, String title, String location, @NotNull(messag 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) { + this.clubId = clubId; + this.title = title; + this.location = location; + this.startTime = startTime; + this.endTime = endTime; + this.minPeople = minPeople; + this.category = category; + this.note = note; + } + + } 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/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/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/ScheduleSearchInput.java deleted file mode 100644 index 91e4d9c..0000000 --- a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchInput.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.example.moim.schedule.dto; - -import lombok.Builder; -import lombok.Data; - -@Data -public class ScheduleSearchInput { - private final Integer date; - private final Long clubId; - private final String search; - private final String category; - - @Builder - public ScheduleSearchInput(Integer date, Long clubId, String search, String category) { - this.date = date; - this.clubId = clubId; - this.search = search; - this.category = category; - } -} diff --git a/src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java new file mode 100644 index 0000000..89c1524 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/dto/ScheduleSearchMonthInput.java @@ -0,0 +1,25 @@ +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 ScheduleSearchMonthInput { + + @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; + private final String category; + + @Builder + public ScheduleSearchMonthInput(Integer date, Long clubId, String search, String category) { + this.date = date; + this.clubId = clubId; + this.search = search; + this.category = category; + } +} 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..a2c91b7 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,23 @@ @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 = "일정 시작 시간을 입력해주세요.") + @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") - @NotNull(message = "일정 종료 시간을 입력해주세요.") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm", timezone = "Asia/Seoul") private LocalDateTime endTime; @Min(value = 1, message = "참여 인원은 1명 이상이어야 합니다") - @NotNull(message = "참여 인원을 입력해주세요.") - private int minPeople;//참여인원수 - @NotBlank(message = "일정 카테고리를 입력해 주세요.") + private Integer minPeople; private String category; private String note; + // 팀 정보 + private String opponentTeamName; + private String opponentTeamAgeRange; @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, String opponentTeamName, String opponentTeamAgeRange) { this.clubId = clubId; - this.id = id; this.title = title; this.location = location; this.startTime = startTime; @@ -43,5 +37,7 @@ public ScheduleUpdateInput(Long clubId, Long id, String title, String location, 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/dto/ScheduleUserOutput.java b/src/main/java/com/example/moim/schedule/dto/ScheduleUserOutput.java index 6e5123b..f7d6907 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/OpponentTeamInfo.java b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java new file mode 100644 index 0000000..be05034 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/entity/OpponentTeamInfo.java @@ -0,0 +1,20 @@ +package com.example.moim.schedule.entity; + +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; +import lombok.Setter; + +@Embeddable +@NoArgsConstructor +@AllArgsConstructor +@Setter @Getter +public class OpponentTeamInfo { + private String opponentTeamName; + @Enumerated(value = EnumType.STRING) + private AgeRange opponentTeamAgeRange; +} 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..4a22003 100644 --- a/src/main/java/com/example/moim/schedule/entity/Schedule.java +++ b/src/main/java/com/example/moim/schedule/entity/Schedule.java @@ -1,12 +1,18 @@ 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; 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; +import org.springframework.security.core.parameters.P; import java.time.LocalDateTime; import java.util.ArrayList; @@ -26,16 +32,20 @@ public class Schedule extends BaseEntity { private LocalDateTime startTime; private LocalDateTime endTime; private int minPeople; - private String category; + @Enumerated(value = EnumType.STRING) + private ScheduleCategory category; private String note; private int attend; private int nonAttend; private Boolean isClose; + private int viewCount; + @Embedded + private OpponentTeamInfo opponentTeamInfo; @OneToMany(mappedBy = "schedule", cascade = CascadeType.REMOVE) - private List comment = new ArrayList<>(); + private List comments = 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(); @@ -43,51 +53,108 @@ public static Schedule createSchedule(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(); } + 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; + schedule.viewCount = 0; 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 + */ 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; } } - 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(); + public void update(ScheduleUpdateInput scheduleUpdateInput) { + 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 = ScheduleCategory.fromKoreanName(scheduleUpdateInput.getCategory()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_SCHEDULE_CATEGORY)); + } if (scheduleUpdateInput.getNote() != null) { this.note = scheduleUpdateInput.getNote(); } + // 이전에 팀 정보가 있었으면 + 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.getOpponentTeamName() != null && scheduleUpdateInput.getOpponentTeamAgeRange() != null) { + this.opponentTeamInfo = new OpponentTeamInfo( + scheduleUpdateInput.getOpponentTeamName(), + AgeRange.fromKoreanName(scheduleUpdateInput.getOpponentTeamAgeRange()).orElseThrow(() -> new ScheduleControllerAdvice(ResponseCode.INVALID_AGE_RANGE)) + ); + } } - public void closeSchedule() { + 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/entity/ScheduleCategory.java b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java new file mode 100644 index 0000000..732eebe --- /dev/null +++ b/src/main/java/com/example/moim/schedule/entity/ScheduleCategory.java @@ -0,0 +1,28 @@ +package com.example.moim.schedule.entity; + +import java.util.Arrays; +import java.util.Optional; + +public enum ScheduleCategory { + REGULAR_TRAINING("정기 운동"), + TOURNAMENT("대회"), + FRIENDLY_MATCH("친선 매치"), // 외부 사용자와 매치 + ETC("기타"), + OFFICIAL_MATCH("정식 매치"); // Match 등록에서 하는 매치 + + 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/repository/ScheduleRepositoryCustom.java b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryCustom.java index d472472..3287564 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,7 @@ public interface ScheduleRepositoryCustom { List findByClubAndTime(Club club, LocalDateTime startTime, LocalDateTime endTime, String search, String category); - Schedule findScheduleById(Long id); + 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 11192de..4da78b4 100644 --- a/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java +++ b/src/main/java/com/example/moim/schedule/repository/ScheduleRepositoryImpl.java @@ -2,13 +2,16 @@ 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; import java.time.LocalDateTime; 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; @@ -22,21 +25,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(); } @@ -47,19 +42,29 @@ private BooleanExpression searchContains(String search) { return null; } - private BooleanExpression categoryContains(String category) { - if (hasText(category)) { - return schedule.category.contains(category); + private BooleanExpression categoryEq(String category) { + Optional scheduleCategory = ScheduleCategory.fromKoreanName(category); + if (hasText(category) && scheduleCategory.isPresent()) { + return schedule.category.eq(scheduleCategory.get()); } return null; } @Override - public Schedule findScheduleById(Long id) { + public Schedule findWithClubById(Long id) { return queryFactory .selectFrom(schedule) .join(schedule.club, club).fetchJoin() .where(schedule.id.eq(id)) .fetchOne(); } + + @Override + public Schedule findByIdWithComment(Long id) { + return queryFactory + .selectFrom(schedule) + .leftJoin(schedule.comments, comment).fetchJoin() + .where(schedule.id.eq(id)) + .fetchOne(); + } } 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..981b64d --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandService.java @@ -0,0 +1,10 @@ +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); + 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 new file mode 100644 index 0000000..545c4bd --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleCommandServiceImpl.java @@ -0,0 +1,103 @@ +package com.example.moim.schedule.service; + +import com.example.moim.club.entity.*; +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.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.*; +import com.example.moim.schedule.comment.entity.Comment; +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.comment.repository.CommentRepository; +import com.example.moim.schedule.repository.ScheduleRepository; +import com.example.moim.schedule.vote.repository.ScheduleVoteRepository; +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; + +import java.util.List; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ScheduleCommandServiceImpl implements ScheduleCommandService { + private final ClubRepository clubRepository; + private final ScheduleRepository scheduleRepository; + private final UserClubRepository userClubRepository; + private final ApplicationEventPublisher eventPublisher; + private final MatchApplicationRepository matchApplicationRepository; + + public ScheduleOutput saveSchedule(ScheduleInput scheduleInput, User user) { + + Club club = getClub(scheduleInput.getClubId()); + + validateClubStaff(getUserClub(club, user)); + + Schedule schedule = scheduleRepository.save(Schedule.from(club, scheduleInput)); + + eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); + + return new ScheduleOutput(schedule); + } + + @Transactional + public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, Long id, User user) { + + Club club = getClub(scheduleUpdateInput.getClubId()); + + validateClubStaff(getUserClub(club, user)); + + Schedule schedule = getSchedule(id); + + schedule.update(scheduleUpdateInput); + + /** + * TODO: 알림 리팩터링 버전으로 다시 적용해야 함 + */ + eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); + + return new ScheduleOutput(schedule); + } + + public String deleteSchedule(Long id, User user) { + + Schedule schedule = getSchedule(id); + + // 운영진인지 권한 확인 + validateClubStaff(getUserClub(schedule.getClub(), user)); + + scheduleRepository.deleteById(id); + + // 알림 + eventPublisher.publishEvent(new ScheduleDeleteEvent(schedule, user)); + + return "스케줄을 정상적으로 취소하였습니다."; + } + + 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)); + } + + private void validateClubStaff(UserClub userClub) { + if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { + throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); + } + } +} 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..5399290 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryService.java @@ -0,0 +1,14 @@ +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.ScheduleSearchMonthInput; +import com.example.moim.user.entity.User; + +import java.util.List; + +public interface ScheduleQueryService { + 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 new file mode 100644 index 0000000..93c0377 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/service/ScheduleQueryServiceImpl.java @@ -0,0 +1,111 @@ +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.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; +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 org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.Month; +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 MatchRepository matchRepository; + + public List findMonthlySchedulesWithFilter(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { + Club club = getClub(scheduleSearchMonthInput.getClubId()); + + 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, + scheduleSearchMonthInput.getSearch(), + scheduleSearchMonthInput.getCategory()) + .stream().map(ScheduleOutput::new).collect(Collectors.toList()); + } + + public List findScheduleByDay(ScheduleSearchMonthInput scheduleSearchMonthInput, User user) { + int year = scheduleSearchMonthInput.getDate() / 10000; + int month = (scheduleSearchMonthInput.getDate() / 100) % 100; + int day = scheduleSearchMonthInput.getDate() % 100; + + 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()); + } + + @Transactional + public ScheduleDetailOutput findScheduleDetail(Long scheduleId, User user) { + Schedule schedule = scheduleRepository.findByIdWithComment(scheduleId); + + getUserClub(schedule.getClub(), user); // 권한 확인 + + // 조회 수 증가 반영 + schedule.increaseViewCount(); + + // 댓글 정보도 함께 포함 + 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())); + } + /** + * 전적 생기면 구현 예정 + */ + else if (schedule.getCategory().equals(ScheduleCategory.OFFICIAL_MATCH)) { + + } + + // 정기 운동, 기타 일정일 때 + return new ScheduleDetailOutput(schedule, comments); + } + + 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)); + } + + 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/service/ScheduleService.java b/src/main/java/com/example/moim/schedule/service/ScheduleService.java deleted file mode 100644 index d46f48b..0000000 --- a/src/main/java/com/example/moim/schedule/service/ScheduleService.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.example.moim.schedule.service; - -import com.example.moim.club.entity.*; -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; -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.entity.Schedule; -import com.example.moim.schedule.entity.ScheduleVote; -import com.example.moim.schedule.exception.advice.ScheduleControllerAdvice; -import com.example.moim.schedule.repository.CommentRepository; -import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.repository.ScheduleVoteRepository; -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; - -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 { - private final ClubRepository clubRepository; - private final ScheduleRepository scheduleRepository; - 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) { - UserClub userClub = userClubRepository.findByClubAndUser(clubRepository.findById(scheduleInput.getClubId()).get(), user).get(); - 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)); - - eventPublisher.publishEvent(new ScheduleSaveEvent(schedule, user)); - return new ScheduleOutput(schedule); - } - - @Transactional - public ScheduleOutput updateSchedule(ScheduleUpdateInput scheduleUpdateInput, 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.updateSchedule(scheduleUpdateInput); - return new ScheduleOutput(schedule); - } - - /** - * TODO: 이름 명확하게 변경하기. findMontSchedule 등으로 - * @param scheduleSearchInput - * @return - */ - public List findSchedule(ScheduleSearchInput scheduleSearchInput) { - return scheduleRepository.findByClubAndTime(clubRepository.findById(scheduleSearchInput.getClubId()).get(), - 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); - return scheduleRepository.findByClubAndTime(clubRepository.findById(scheduleSearchInput.getClubId()).get(), - 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(); - return new ScheduleDetailOutput(schedule, -// scheduleVoteRepository.findBySchedule(schedule).stream().map(ScheduleUserOutput::new).toList(), - matchApplicationRepository.findBySchedule(schedule).stream().map(MatchApplyClubOutput::new).toList()); - } - - /** - * TODO: void -> 기본 응답 - * @param scheduleVoteInput - * @param user - */ - @Transactional - public void voteSchedule(ScheduleVoteInput scheduleVoteInput, User user) { - Schedule schedule = scheduleRepository.findScheduleById(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()); - } -// if (scheduleVoteInput.getAttendance().equals("attend")) { -// eventPublisher.publishEvent(new ScheduleVoteEvent(schedule, user)); -// } - } - - /** - * TODO: void -> 기본 응답 - * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) - * @param id - */ - public void deleteSchedule(Long id) { - scheduleRepository.deleteById(id); - } - - /** - * TODO: void -> 기본 응답 - * FIXME: 운영진인지 아닌지 체크하는 로직 필요함(필터에서 걸러주면 필요 X) - * @param id - */ - public void voteEncourage(Long id) { - Schedule schedule = scheduleRepository.findScheduleById(id); - List userList = userClubRepository.findUserByClub(schedule.getClub()).stream().map(UserClub::getUser).toList(); - eventPublisher.publishEvent(new ScheduleEncourageEvent(schedule, userList)); - } - - /** - * TODO: void -> 기본 응답, 이름 명확하게 바꾸기 closeScheduleVote 등 - * @param id - */ - @Transactional - public void closeSchedule(Long id, User user) { - Schedule schedule = scheduleRepository.findById(id).get(); - UserClub userClub = userClubRepository.findByClubAndUser(schedule.getClub(), user).get(); - if (!(userClub.getClubRole().equals(ClubRole.STAFF))) { - throw new ScheduleControllerAdvice(ResponseCode.CLUB_PERMISSION_DENIED); - } - - schedule.closeSchedule(); - } - -// public void saveComment(CommentInput commentInput, User user) { -// commentRepository.save(Comment.createComment(user, scheduleRepository.findById(commentInput.getId()).get(), commentInput.getContents())); -// } -} 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..39ddd68 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteController.java @@ -0,0 +1,46 @@ +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.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.*; + +@RestController +@RequiredArgsConstructor +public class ScheduleVoteController implements ScheduleVoteControllerDocs { + + private final ScheduleVoteService scheduleVoteService; + + 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/{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/{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 new file mode 100644 index 0000000..a4fb198 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/controller/ScheduleVoteControllerDocs.java @@ -0,0 +1,20 @@ +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; +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 = "참여, 불참") + 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/dto/ScheduleVoteInput.java b/src/main/java/com/example/moim/schedule/vote/dto/ScheduleVoteInput.java similarity index 54% 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..0fac756 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,16 +1,16 @@ -package com.example.moim.schedule.dto; +package com.example.moim.schedule.vote.dto; 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/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..57a8803 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteService.java @@ -0,0 +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 { + 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 new file mode 100644 index 0000000..7606250 --- /dev/null +++ b/src/main/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImpl.java @@ -0,0 +1,94 @@ +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.BaseResponse; +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.notification.dto.ScheduleEncourageEvent; +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; +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; + + @Transactional + 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(); + + // 회원인지 확인 + getUserClub(schedule.getClub(), user); + + //투표 처음이면 + 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); + } + + return schedule.getTitle() + "에 투표를 완료했습니다."; + } + + 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)); + + return schedule.getTitle() + "의 투표 독려 알림을 보냈습니다."; + } + + @Transactional + public String closeScheduleVote(Long id, User user) { + Schedule schedule = scheduleRepository.findWithClubById(id); + + // 운영진인지 검증 + validateClubStaff(getUserClub(schedule.getClub(), user)); + + schedule.close(); + + return schedule.getTitle() + "의 투표를 마감했습니다."; + } + + 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)); + } + + 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/repository/ClubRepositoryImplTest.java b/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java index 4897e36..deff3a8 100644 --- a/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java +++ b/src/test/java/com/example/moim/club/repository/ClubRepositoryImplTest.java @@ -62,8 +62,8 @@ void init() { .storedFileName("test/aaaa-aaaa-aaaa.jpg") .build(); - Club savedClub = clubRepository.save(Club.createClub(clubInput, fileInfo)); - Club savedClub2 = clubRepository.save(Club.createClub(clubInput2, fileInfo)); + Club savedClub = clubRepository.save(Club.from(clubInput, fileInfo)); + Club savedClub2 = clubRepository.save(Club.from(clubInput2, fileInfo)); 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 f58d796..b6ad7a0 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 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)); @@ -273,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 @@ -295,7 +293,7 @@ void delete_ClubUser_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 @@ -312,7 +310,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 @@ -332,7 +330,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 @@ -352,7 +350,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 f134be5..4821d76 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 52f3174..7bab238 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; @@ -81,7 +84,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()); // notice - createAt 강제로 주입하기 ReflectionTestUtils.setField(notice, "createdDate", LocalDateTime.now()); 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 5ca199f..ba43940 100644 --- a/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java +++ b/src/test/java/com/example/moim/club/service/NoticeQueryServiceImplTest.java @@ -88,7 +88,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()); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); @@ -113,7 +113,7 @@ void findNotice() { @DisplayName("동아리에 등록된 공지가 없을 경우 빈 리스트를 반환한다") void findNotice_zero_notice() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); User user = User.createUser(signupInput); UserClub userClub = UserClub.createUserClub(user, club); @@ -135,7 +135,7 @@ void findNotice_zero_notice() { @DisplayName("동아리에 등록된 공지를 조회하면 나중에 저장된 순으로 정렬되어 있다.") void findNotice_sort() { //given - Club club = Club.createClub(clubInput, null); + Club club = Club.from(clubInput, null); Notice notice = Notice.createNotice(club, noticeInput.getTitle(), noticeInput.getContent()); ReflectionTestUtils.setField(notice, "id", 1L); LocalDateTime localDateTime = LocalDateTime.now(); diff --git a/src/test/java/com/example/moim/match/MatchServiceTest.java b/src/test/java/com/example/moim/match/MatchServiceTest.java index 0b1ae4c..2a197d8 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; @@ -19,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; @@ -86,7 +84,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/notification/service/NotificationServiceTest.java b/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java index d4f2c68..8e79bbd 100644 --- a/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java +++ b/src/test/java/com/example/moim/notification/service/NotificationServiceTest.java @@ -159,7 +159,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/comment/service/ScheduleCommentCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java new file mode 100644 index 0000000..dbd4a51 --- /dev/null +++ b/src/test/java/com/example/moim/schedule/comment/service/ScheduleCommentCommandServiceImplTest.java @@ -0,0 +1,124 @@ +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.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; +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; + private FileInfo fileInfo; + + @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(); + + // fileInfo 생성 + this.fileInfo = FileInfo.builder().fileUrl("fileUrl").storedFileName("storedFileName").originalFileName("originalFileName").build(); + } + + @Test + @DisplayName("회원은 일정에 댓글을 달 수 있다.") + void saveComment() { + // given + Club club = Club.from(clubInput, fileInfo); + 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, fileInfo); + 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/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/ScheduleRepositoryImplTest.java b/src/test/java/com/example/moim/schedule/repository/ScheduleRepositoryImplTest.java index 3d5294e..3a4095b 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({ @@ -33,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); @@ -45,14 +44,44 @@ 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 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("회사 면접이 잡혀있어서 못 갑니다"); + assertThat(result.getComments().get(0).getContents()).isEqualTo("회사 면접이 잡혀있어서 못 갑니다"); } } \ No newline at end of file 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..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; @@ -12,7 +13,6 @@ import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.*; @DataJpaTest @SqlGroup({ @@ -30,7 +30,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/ScheduleCommandServiceImplTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java new file mode 100644 index 0000000..4d4e4ac --- /dev/null +++ b/src/test/java/com/example/moim/schedule/service/ScheduleCommandServiceImplTest.java @@ -0,0 +1,178 @@ +package com.example.moim.schedule.service; + +import com.example.moim.club.dto.request.ClubInput; +import com.example.moim.club.entity.*; +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.notification.dto.ScheduleSaveEvent; +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; +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.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ScheduleCommandServiceImplTest { + + @Mock + private ClubRepository clubRepository; + @Mock + private ScheduleRepository scheduleRepository; + @Mock + private UserClubRepository userClubRepository; + @Mock + private ApplicationEventPublisher applicationEventPublisher; + @InjectMocks + private ScheduleCommandServiceImpl scheduleCommandService; + + // 필요한 공동 객체 + private ScheduleInput scheduleInput; + private SignupInput signupInput; + private ClubInput clubInput; + private ScheduleUpdateInput scheduleUpdateInput; + + @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(); + 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("친선 매치").note("note").build(); + } + + @Test + @DisplayName("운영진은 일정을 생성할 수 있다") + void saveSchedule() { + //given + Club club = Club.from(clubInput, null); + Schedule schedule = Schedule.from(club, scheduleInput); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + //when + 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 = scheduleCommandService.saveSchedule(scheduleInput, user); + //then + assertThat(scheduleOutput.getTitle()).isEqualTo("title"); + assertThat(scheduleOutput.getMinPeople()).isEqualTo(10); + assertThat(scheduleOutput.getNote()).isEqualTo("note"); + 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)); + } + + @Test + @DisplayName("일반 멤버는 일정을 생성할 때 예외가 발생한다") + void saveSchedule_wrong_permission() { + //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(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.of(userClub)); + //then + Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { + scheduleCommandService.saveSchedule(scheduleInput, user); + }); + assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + } + + @Test + @DisplayName("운영진은 일정을 변경할 수 있다") + void updateSchedule() { + //given + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createLeaderUserClub(user, club); + 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)); + when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); + ScheduleOutput scheduleOutput = scheduleCommandService.updateSchedule(scheduleUpdateInput, 1L, user); + //then + assertThat(scheduleOutput.getTitle()).isEqualTo("update title"); + assertThat(scheduleOutput.getLocation()).isEqualTo("update location"); + verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); + verify(clubRepository, times(1)).findById(any(Long.class)); + verify(scheduleRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일반 멤버는 일정을 수정할 때 예외가 발생한다") + void updateSchedule_wrong_permission() { + //given + Club club = Club.from(clubInput, null); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + //when + when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); + //then + Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { + 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 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 + 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 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..c713a10 --- /dev/null +++ b/src/test/java/com/example/moim/schedule/service/ScheduleQueryServiceImplTest.java @@ -0,0 +1,183 @@ +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.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; +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; +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.junit.jupiter.api.Assertions.assertThrows; +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 UserClubRepository userClubRepository; + @Mock + private MatchApplicationRepository matchApplicationRepository; + @InjectMocks + private ScheduleQueryServiceImpl scheduleQueryService; + + // 필요한 공동 객체 + 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)) + .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); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + Schedule schedule = Schedule.from(club, scheduleInput); + 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)); + 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); + 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); + 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()); + 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); + 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); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + Schedule schedule = Schedule.from(club, scheduleInput); + 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)); + 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); + 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); + User user = User.createUser(signupInput); + UserClub userClub = UserClub.createUserClub(user, club); + Schedule schedule = Schedule.from(club, scheduleInput); + schedule.setCreatedDate(); + schedule.setUpdatedDate(); + //when + 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)).findByIdWithComment(any(Long.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.findByIdWithComment(any(Long.class))).thenReturn(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/java/com/example/moim/schedule/service/ScheduleServiceTest.java b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java index 47ff8c2..e69de29 100644 --- a/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java +++ b/src/test/java/com/example/moim/schedule/service/ScheduleServiceTest.java @@ -1,358 +0,0 @@ -package com.example.moim.schedule.service; - -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.*; -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; -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.exception.advice.ScheduleControllerAdvice; -import com.example.moim.schedule.repository.ScheduleRepository; -import com.example.moim.schedule.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.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class ScheduleServiceTest { - - @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 ScheduleService scheduleService; - - // 필요한 공동 객체 - private ScheduleInput scheduleInput; - private SignupInput signupInput; - private ClubInput clubInput; - private ScheduleUpdateInput scheduleUpdateInput; - - @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("soccer") - .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").clubCheckPassword("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") - .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(); - } - - @Test - @DisplayName("운영진은 일정을 생성할 수 있다") - void saveSchedule() { - //given - Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); - User user = User.createUser(signupInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - //when - 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); - //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(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)); - } - - @Test - @DisplayName("일반 멤버는 일정을 생성할 때 예외가 발생한다") - void saveSchedule_wrong_permission() { - //given - Club club = Club.createClub(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(userClubRepository.findByClubAndUser(club, user)).thenReturn(Optional.of(userClub)); - //then - Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.saveSchedule(scheduleInput, user); - }); - assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); - verify(clubRepository, times(1)).findById(any(Long.class)); - verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); - } - - @Test - @DisplayName("운영진은 일정을 변경할 수 있다") - void updateSchedule() { - //given - Club club = Club.createClub(clubInput, null); - User user = User.createUser(signupInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - Schedule schedule = Schedule.createSchedule(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)); - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - ScheduleOutput scheduleOutput = scheduleService.updateSchedule(scheduleUpdateInput, user); - //then - assertThat(scheduleOutput.getTitle()).isEqualTo("update title"); - assertThat(scheduleOutput.getLocation()).isEqualTo("update location"); - verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); - verify(clubRepository, times(1)).findById(any(Long.class)); - verify(scheduleRepository, times(1)).findById(any(Long.class)); - } - - @Test - @DisplayName("일반 멤버는 일정을 수정할 때 예외가 발생한다") - void updateSchedule_wrong_permission() { - //given - Club club = Club.createClub(clubInput, null); - User user = User.createUser(signupInput); - UserClub userClub = UserClub.createUserClub(user, club); - //when - when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - when(clubRepository.findById(any(Long.class))).thenReturn(Optional.of(club)); - //then - Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.updateSchedule(scheduleUpdateInput, 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.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(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.findSchedule(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.createClub(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.findSchedule(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.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(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.createClub(clubInput, null); - MatchApplication matchApplication = MatchApplication.applyMatch(new Match(), club); - Schedule schedule = Schedule.createSchedule(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("멤버는 일정 참가에 대해 재투표를 할 수 있다") - void voteSchedule_re() { - //given - Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(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.findScheduleById(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(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); - } - - @Test - @DisplayName("멤버는 일정 참가에 대해 투표를 할 수 있다") - void voteSchedule() { - //given - Club club = Club.createClub(clubInput, null); - Schedule schedule = Schedule.createSchedule(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.findScheduleById(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(scheduleVoteRepository, times(1)).findByScheduleAndUser(any(Schedule.class), any(User.class)); - verify(scheduleVoteRepository, times(1)).save(any(ScheduleVote.class)); - } - - @Test - @DisplayName("일정을 삭제할 수 있다") - void deleteSchedule() { - //given - Long id = 1L; - //when - scheduleService.deleteSchedule(id); - //then - verify(scheduleRepository, times(1)).deleteById(any(Long.class)); - } - - @Test - @DisplayName("운영진은 투표를 독려할 수 있다") - void voteEncourage() { - //given - Long id = 1L; - Club club = Club.createClub(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - //when - when(scheduleRepository.findScheduleById(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(userClubRepository, times(1)).findUserByClub(any(Club.class)); - verify(applicationEventPublisher, times(1)).publishEvent(any(ScheduleEncourageEvent.class)); - } - - @Test - @DisplayName("운영진은 일정 투표를 마감할 수 있다") - void closeSchedule() { - //given - Club club = Club.createClub(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); - UserClub userClub = UserClub.createLeaderUserClub(user, club); - //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - scheduleService.closeSchedule(1L, user); - //then - verify(scheduleRepository, times(1)).findById(any(Long.class)); - verify(userClubRepository, times(1)).findByClubAndUser(any(Club.class), any(User.class)); - } - - @Test - @DisplayName("일반 회원이 일정 투표를 마감할 때 예외가 발생한다") - void closeSchedule_wrong_permission() { - //given - Club club = Club.createClub(clubInput, null); - User user = User.createUser(signupInput); - Schedule schedule = Schedule.createSchedule(club, scheduleInput); - UserClub userClub = UserClub.createUserClub(user, club); - //when - when(scheduleRepository.findById(any(Long.class))).thenReturn(Optional.of(schedule)); - when(userClubRepository.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); - //then - Exception exception = assertThrows(ScheduleControllerAdvice.class, () -> { - scheduleService.closeSchedule(1L, user); - }); - assertThat(exception.getMessage()).isEqualTo(ResponseCode.CLUB_PERMISSION_DENIED.getMessage()); - verify(scheduleRepository, times(1)).findById(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/vote/service/ScheduleVoteServiceImplTest.java b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java new file mode 100644 index 0000000..0c6b765 --- /dev/null +++ b/src/test/java/com/example/moim/schedule/vote/service/ScheduleVoteServiceImplTest.java @@ -0,0 +1,179 @@ +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.UserClubRepository; +import com.example.moim.global.enums.*; +import com.example.moim.global.exception.ResponseCode; +import com.example.moim.notification.dto.ScheduleEncourageEvent; +import com.example.moim.schedule.dto.ScheduleInput; +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; +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.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; + +@ExtendWith(MockitoExtension.class) +class ScheduleVoteServiceImplTest { + + @Mock + private ScheduleRepository scheduleRepository; + @Mock + private UserClubRepository userClubRepository; + @Mock + private ScheduleVoteRepository scheduleVoteRepository; + @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().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, 1L, 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().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, 1L, 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.findByClubAndUser(any(Club.class), any(User.class))).thenReturn(Optional.of(userClub)); + when(userClubRepository.findUserByClub(club)).thenReturn(List.of(userClub)); + scheduleVoteService.voteEncourage(id, user); + //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() { + //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 diff --git a/src/test/resources/sql/comment-repository-test-data.sql b/src/test/resources/sql/comment-repository-test-data.sql index 1f2d8c1..658ba76 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, 'soccor', '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); +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 4c9cd33..11b7a54 100644 --- a/src/test/resources/sql/schedule-vote-repository-test-data.sql +++ b/src/test/resources/sql/schedule-vote-repository-test-data.sql @@ -22,15 +22,15 @@ 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, 'soccor', '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); +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, '회사 면접이 잡혀있어서 못 갑니다'); -- 투표 생성 -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);