From 5886a8e59a02ff0dc8fa02b4464a3fbc0314e763 Mon Sep 17 00:00:00 2001 From: minkyeong Date: Fri, 29 Nov 2024 17:30:48 +0900 Subject: [PATCH] mission: submit week9 mission --- .../controller/MissionController.java | 65 +++++++++++++++++- .../controller/ReviewController.java | 15 ++++- .../domain/mapping/MemberMission.java | 34 ++++++++-- .../repository/MemberMissionRepository.java | 12 +++- .../repository/MissionRepository.java | 4 ++ .../repository/ReviewRepository.java | 5 ++ .../MissionService/MissionService.java | 67 +++++++++++++++++-- .../service/ReviewService/ReviewService.java | 18 +++++ .../validation/annotation/PageValidation.java | 19 ++++++ .../validation/validator/PageValidator.java | 13 ++++ 10 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 src/main/java/javalab/umc7th_mission/validation/annotation/PageValidation.java create mode 100644 src/main/java/javalab/umc7th_mission/validation/validator/PageValidator.java diff --git a/src/main/java/javalab/umc7th_mission/controller/MissionController.java b/src/main/java/javalab/umc7th_mission/controller/MissionController.java index 68f18b8..35494e7 100644 --- a/src/main/java/javalab/umc7th_mission/controller/MissionController.java +++ b/src/main/java/javalab/umc7th_mission/controller/MissionController.java @@ -1,6 +1,7 @@ -package javalab.umc7th_mission.controller; +package umc.spring.controller; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -9,6 +10,7 @@ import umc.spring.service.MissionService.MissionService; import jakarta.validation.Valid; +import umc.spring.validation.annotation.PageValidation; @RestController @RequiredArgsConstructor @@ -31,4 +33,65 @@ public ResponseEntity addMission(@RequestBody @Valid MissionRequestDTO reques return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 에러: " + e.getMessage()); } } + // 특정 가게의 미션 목록 조회 + @GetMapping("/store/{storeId}") + public ResponseEntity> getMissionsByStore( + @PathVariable Long storeId, + @RequestParam("page") @PageValidation Integer page, + @RequestParam("size") Integer size + ) { + Page missions = missionService.getMissionsByStore(storeId, page, size); + return ResponseEntity.ok(missions); + } + + // 특정 회원이 진행 중인 미션 목록 조회 + @GetMapping("/member/{memberId}/missions") + public ResponseEntity> getOngoingMissionsByMember( + @PathVariable Long memberId, + @RequestParam("page") @PageValidation Integer page, + @RequestParam("size") Integer size + ) { + try { + Page missions = missionService.getOngoingMissionsByMember(memberId, page, size); + return ResponseEntity.ok(missions); + } catch (IllegalArgumentException e) { + // 회원 ID가 잘못된 경우 + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Page.empty()); + } catch (Exception e) { + // 기타 서버 오류 + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(Page.empty()); + } + } + + // 회원이 특정 미션에 도전하기 + @PostMapping("/{missionId}/start") + public ResponseEntity startMission( + @PathVariable Long missionId, + @RequestParam("memberId") Long memberId) { + try { + missionService.startMission(memberId, missionId); + return ResponseEntity.ok("미션 도전이 성공적으로 시작되었습니다."); + } catch (IllegalArgumentException e) { + // 클라이언트 요청 오류 + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } catch (Exception e) { + // 서버 내부 오류 + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 에러: " + e.getMessage()); + } + } + + // 회원이 특정 미션 완료하기 + @PostMapping("/{missionId}/complete") + public ResponseEntity completeMission( + @PathVariable Long missionId, + @RequestParam("memberId") Long memberId) { + try { + missionService.completeMission(memberId, missionId); + return ResponseEntity.ok("미션이 성공적으로 완료되었습니다."); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("서버 에러: " + e.getMessage()); + } + } } diff --git a/src/main/java/javalab/umc7th_mission/controller/ReviewController.java b/src/main/java/javalab/umc7th_mission/controller/ReviewController.java index d79ace2..682c518 100644 --- a/src/main/java/javalab/umc7th_mission/controller/ReviewController.java +++ b/src/main/java/javalab/umc7th_mission/controller/ReviewController.java @@ -1,11 +1,13 @@ -package javalab.umc7th_mission.controller; +package umc.spring.controller; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import umc.spring.dto.ReviewRequestDTO; import umc.spring.dto.ReviewResponseDTO; import umc.spring.service.ReviewService.ReviewService; +import umc.spring.validation.annotation.PageValidation; @RestController @RequestMapping("/review") @@ -20,4 +22,15 @@ public ResponseEntity addReview(@RequestBody ReviewRequestDTO ReviewResponseDTO response = reviewService.addReview(request); return ResponseEntity.ok(response); } + + // 내가 작성한 리뷰 목록 조회 API + @GetMapping("/my") + public ResponseEntity> getMyReviews( + @RequestParam("page") @PageValidation Integer page, + @RequestParam("size") Integer size, + @RequestParam("memberId") Long memberId // 회원 ID를 요청 파라미터로 받음 + ) { + Page reviews = reviewService.getMyReviews(memberId, page, size); + return ResponseEntity.ok(reviews); + } } \ No newline at end of file diff --git a/src/main/java/javalab/umc7th_mission/domain/mapping/MemberMission.java b/src/main/java/javalab/umc7th_mission/domain/mapping/MemberMission.java index 8ff3194..b8bc723 100644 --- a/src/main/java/javalab/umc7th_mission/domain/mapping/MemberMission.java +++ b/src/main/java/javalab/umc7th_mission/domain/mapping/MemberMission.java @@ -1,13 +1,13 @@ -package javalab.umc7th_mission.domain.mapping; +package umc.spring.domain.mapping; import jakarta.persistence.*; import lombok.*; -import javalab.umc7th_mission.domain.Member; -import javalab.umc7th_mission.domain.Mission; -import javalab.umc7th_mission.domain.common.BaseEntity; -import javalab.umc7th_mission.domain.enums.MissionStatus; +import umc.spring.domain.Member; +import umc.spring.domain.Mission; +import umc.spring.domain.common.BaseEntity; +import umc.spring.domain.enums.MissionStatus; @Entity @Getter @@ -25,13 +25,33 @@ public class MemberMission extends BaseEntity { private MissionStatus status; //오류 수정 - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "mission_id") private Mission mission; + @Builder + public MemberMission(Member member, Mission mission, MissionStatus status) { + this.member = member; + this.mission = mission; + this.status = status; + } + + // Setter를 대신하는 빌더로 새로운 객체 생성 + public static MemberMission create(Member member, Mission mission, MissionStatus status) { + return MemberMission.builder() + .member(member) + .mission(mission) + .status(status) + .build(); + } + + // 미션 완료 처리 + public void complete() { + this.status = MissionStatus.COMPLETE; + } } diff --git a/src/main/java/javalab/umc7th_mission/repository/MemberMissionRepository.java b/src/main/java/javalab/umc7th_mission/repository/MemberMissionRepository.java index 8b217af..8ecfa7e 100644 --- a/src/main/java/javalab/umc7th_mission/repository/MemberMissionRepository.java +++ b/src/main/java/javalab/umc7th_mission/repository/MemberMissionRepository.java @@ -1,14 +1,22 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Member; import umc.spring.domain.Mission; +import org.springframework.data.domain.Pageable; import umc.spring.domain.mapping.MemberMission; import umc.spring.domain.enums.MissionStatus; -public interface MemberMissionRepository extends JpaRepository { +import java.util.Optional; +public interface MemberMissionRepository extends JpaRepository { + // 특정 회원이 특정 미션에 도전 중인지 확인 boolean existsByMemberIdAndMissionIdAndStatus(Long memberId, Long missionId, MissionStatus status); - boolean existsByMissionIdAndStatus(Long missionId, MissionStatus missionStatus); + + // 회원-미션 관계 조회 + Optional findByMemberIdAndMissionId(Long memberId, Long missionId); + // 특정 회원의 진행 중인 미션 목록 조회 + Page findByMemberIdAndStatus(Long memberId, MissionStatus status, Pageable pageable); } diff --git a/src/main/java/javalab/umc7th_mission/repository/MissionRepository.java b/src/main/java/javalab/umc7th_mission/repository/MissionRepository.java index 1b043da..fabaa97 100644 --- a/src/main/java/javalab/umc7th_mission/repository/MissionRepository.java +++ b/src/main/java/javalab/umc7th_mission/repository/MissionRepository.java @@ -1,10 +1,14 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaRepository; import umc.spring.domain.Mission; +import org.springframework.data.domain.Pageable; import umc.spring.domain.enums.MissionStatus; public interface MissionRepository extends JpaRepository { // 미션이 이미 도전 중인지 확인 boolean existsByStatusAndId(MissionStatus status, Long id); + // 특정 가게의 미션 목록 조회 + Page findByStoreId(Long storeId, Pageable pageable); } diff --git a/src/main/java/javalab/umc7th_mission/repository/ReviewRepository.java b/src/main/java/javalab/umc7th_mission/repository/ReviewRepository.java index 5589b48..4022556 100644 --- a/src/main/java/javalab/umc7th_mission/repository/ReviewRepository.java +++ b/src/main/java/javalab/umc7th_mission/repository/ReviewRepository.java @@ -1,9 +1,14 @@ package umc.spring.repository; +import org.springframework.data.domain.Page; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import umc.spring.domain.Review; +import org.springframework.data.domain.Pageable; @Repository public interface ReviewRepository extends JpaRepository { + + // 회원 ID로 리뷰 조회 (페이징 포함) + Page findByMemberId(Long memberId, Pageable pageable); } diff --git a/src/main/java/javalab/umc7th_mission/service/MissionService/MissionService.java b/src/main/java/javalab/umc7th_mission/service/MissionService/MissionService.java index 187abce..5e46524 100644 --- a/src/main/java/javalab/umc7th_mission/service/MissionService/MissionService.java +++ b/src/main/java/javalab/umc7th_mission/service/MissionService/MissionService.java @@ -2,6 +2,8 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import umc.spring.apiPayload.exception.GeneralException; import umc.spring.domain.Member; @@ -58,7 +60,7 @@ public MissionResponseDTO addMission(MissionRequestDTO request) { @Transactional public void startMission(Long memberId, Long missionId) { - // 1. Member와 Mission 조회 + // 1. 회원 및 미션 조회 Member member = memberRepository.findById(memberId) .orElseThrow(() -> new GeneralException(MEMBER_NOT_FOUND)); Mission mission = missionRepository.findById(missionId) @@ -68,13 +70,70 @@ public void startMission(Long memberId, Long missionId) { boolean isAlreadyChallenging = memberMissionRepository.existsByMemberIdAndMissionIdAndStatus( memberId, missionId, MissionStatus.CHALLENGING ); - if (isAlreadyChallenging) { - throw new GeneralException(_FORBIDDEN); + throw new GeneralException(MEMBER_NOT_FOUND); } - // 3. MemberMission 객체 생성 및 저장 + // 3. 새로운 도전 상태 추가 MemberMission memberMission = MemberMission.create(member, mission, MissionStatus.CHALLENGING); memberMissionRepository.save(memberMission); } + + // 특정 가게의 미션 목록 조회 + public Page getMissionsByStore(Long storeId, Integer page, Integer size) { + // Store 유효성 검사 + storeRepository.findById(storeId) + .orElseThrow(() -> new IllegalArgumentException("해당 Store를 찾을 수 없습니다.")); + + // 페이징 처리 + PageRequest pageable = PageRequest.of(page, size); + + // 미션 목록 조회 및 DTO 변환 + return missionRepository.findByStoreId(storeId, pageable) + .map(mission -> MissionResponseDTO.builder() + .id(mission.getId()) + .missionSpec(mission.getMissionSpec()) + .reward(mission.getReward()) + .deadline(mission.getDeadline()) + .storeName(mission.getStore().getName()) + .build()); + } + + @Transactional + public void completeMission(Long memberId, Long missionId) { + // 회원-미션 관계 조회 + MemberMission memberMission = memberMissionRepository.findByMemberIdAndMissionId(memberId, missionId) + .orElseThrow(() -> new GeneralException(MEMBER_NOT_FOUND)); + + // 현재 상태 확인 (CHALLENGING 상태여야 완료 가능) + if (memberMission.getStatus() != MissionStatus.CHALLENGING) { + throw new GeneralException(MEMBER_NOT_FOUND); + } + + // 미션 완료 처리 + memberMission.complete(); + } + + @Transactional + public Page getOngoingMissionsByMember(Long memberId, Integer page, Integer size) { + // 1. 회원 유효성 검사 + memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("해당 회원을 찾을 수 없습니다.")); + + // 2. 페이징 처리 + PageRequest pageable = PageRequest.of(page - 1, size); // Spring Data JPA는 0-based index 사용 + + // 3. 진행 중인 미션 조회 및 변환 + return memberMissionRepository.findByMemberIdAndStatus(memberId, MissionStatus.CHALLENGING, pageable) + .map(memberMission -> { + Mission mission = memberMission.getMission(); + return MissionResponseDTO.builder() + .id(mission.getId()) + .missionSpec(mission.getMissionSpec()) + .reward(mission.getReward()) + .deadline(mission.getDeadline()) + .storeName(mission.getStore().getName()) + .build(); + }); + } } \ No newline at end of file diff --git a/src/main/java/javalab/umc7th_mission/service/ReviewService/ReviewService.java b/src/main/java/javalab/umc7th_mission/service/ReviewService/ReviewService.java index 86592c7..9066c4b 100644 --- a/src/main/java/javalab/umc7th_mission/service/ReviewService/ReviewService.java +++ b/src/main/java/javalab/umc7th_mission/service/ReviewService/ReviewService.java @@ -2,6 +2,8 @@ import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import umc.spring.apiPayload.code.status.ErrorStatus; import umc.spring.apiPayload.exception.GeneralException; @@ -52,4 +54,20 @@ public ReviewResponseDTO addReview(ReviewRequestDTO request) { .memberId(member.getId()) .build(); } + + // 내가 작성한 리뷰 목록 조회 + public Page getMyReviews(Long memberId, Integer page, Integer size) { + // 페이징 처리 (PageRequest 생성) + PageRequest pageable = PageRequest.of(page, size); + + // 리뷰 조회 및 DTO 변환 + return reviewRepository.findByMemberId(memberId, pageable) + .map(review -> ReviewResponseDTO.builder() + .id(review.getId()) + .title(review.getTitle()) + .score(review.getScore()) + .storeId(review.getStore().getId()) + .memberId(review.getMember().getId()) + .build()); + } } diff --git a/src/main/java/javalab/umc7th_mission/validation/annotation/PageValidation.java b/src/main/java/javalab/umc7th_mission/validation/annotation/PageValidation.java new file mode 100644 index 0000000..416d69b --- /dev/null +++ b/src/main/java/javalab/umc7th_mission/validation/annotation/PageValidation.java @@ -0,0 +1,19 @@ +package umc.spring.validation.annotation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import umc.spring.validation.validator.PageValidator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Constraint(validatedBy = PageValidator.class) +@Target({ ElementType.PARAMETER }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PageValidation { + String message() default "Page number must be greater than 0."; + Class[] groups() default {}; + Class[] payload() default {}; +} diff --git a/src/main/java/javalab/umc7th_mission/validation/validator/PageValidator.java b/src/main/java/javalab/umc7th_mission/validation/validator/PageValidator.java new file mode 100644 index 0000000..8dc9512 --- /dev/null +++ b/src/main/java/javalab/umc7th_mission/validation/validator/PageValidator.java @@ -0,0 +1,13 @@ +package umc.spring.validation.validator; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import umc.spring.validation.annotation.PageValidation; + +public class PageValidator implements ConstraintValidator { + + @Override + public boolean isValid(Integer value, ConstraintValidatorContext context) { + return value != null && value > 0; // 페이지 번호는 1 이상이어야 유효 + } +}