From 9a7b1caddff3eee47a9b5c400e07ce29e9d3cac2 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Tue, 25 Nov 2025 01:26:29 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Week9]=20Feat:=20=EA=B0=80=EA=B2=8C=20?= =?UTF-8?q?=EB=A6=AC=EB=B7=B0=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../review/repository/ReviewRepository.java | 19 +++++++++++ .../domain/review/service/ReviewService.java | 2 ++ .../review/service/ReviewServiceImpl.java | 34 +++++++++++++++++++ .../store/controller/StoreController.java | 20 +++++++++++ 4 files changed, 75 insertions(+) diff --git a/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java b/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java index cde7845..d793dd5 100644 --- a/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java +++ b/src/main/java/com/example/umc9th/domain/review/repository/ReviewRepository.java @@ -2,6 +2,8 @@ import com.example.umc9th.domain.member.entity.Member; import com.example.umc9th.domain.review.entity.Review; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -28,4 +30,21 @@ void createReview(@Param("memberId") Long memberId, @Param("storeId") Long storeId, @Param("content") String content, @Param("rate") Float rate); + + + /** + * 가게별 리뷰 목록 조회 (커서 기반 페이징) + * - storeId: 해당 가게의 리뷰만 조회 + * - cursorId: 마지막으로 조회한 리뷰 ID (첫 페이지는 NULL) + * - Pageable: 페이징 사이즈(LIMIT) 처리 + */ + @Query("SELECT r FROM Review r " + + "WHERE r.store.id = :storeId " + + " AND (:cursorId IS NULL OR r.id < :cursorId) " + + "ORDER BY r.id DESC") + Slice findReviewsByStore( + @Param("storeId") Long storeId, + @Param("cursorId") Long cursorId, + Pageable pageable + ); } diff --git a/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java b/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java index 915e1b8..fe92c5c 100644 --- a/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java +++ b/src/main/java/com/example/umc9th/domain/review/service/ReviewService.java @@ -15,4 +15,6 @@ ReviewResponse.ReviewListDTO getMyReviews( Integer rate, Long cursorId ); + + ReviewResponse.ReviewListDTO getReviewList(Long storeId, Long cursorId); } diff --git a/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java b/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java index b666676..8a571be 100644 --- a/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/review/service/ReviewServiceImpl.java @@ -14,6 +14,8 @@ import com.example.umc9th.global.apiPayload.exception.handler.ErrorHandler; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -32,6 +34,8 @@ public class ReviewServiceImpl implements ReviewService { private final ReviewRepository reviewRepository; private final ReviewRepositoryCustom reviewRepositoryCustom; + private static final int PAGE_SIZE = 10; + // 리뷰 작성 @Transactional @Override @@ -89,5 +93,35 @@ public ReviewResponse.ReviewListDTO getMyReviews( .build(); } + // 가게별 리뷰 조회 + public ReviewResponse.ReviewListDTO getReviewList(Long storeId, Long cursorId) { + + Pageable pageable = PageRequest.of(0, PAGE_SIZE); + + Slice reviewSlice = reviewRepository.findReviewsByStore(storeId, cursorId, pageable); + + // 리뷰 ID 리스트 추출 + List reviewIds = reviewSlice.getContent().stream() + .map(Review::getId) + .toList(); + + // 리뷰별 이미지 Map 조회 (N+1 문제 방지를 위해 별도의 Batch 조회) + Map> reviewImageMap = reviewRepositoryCustom.findReviewImages(reviewIds); + + List dtoList = + ReviewConverter.toReviewResultDTOList(reviewSlice.getContent(), reviewImageMap); + + Long nextCursorId = null; + if (reviewSlice.hasNext() && !dtoList.isEmpty()) { + // 다음 페이지가 존재하고 DTO 리스트가 비어있지 않은 경우 + nextCursorId = dtoList.get(dtoList.size() - 1).getReviewId(); + } + + return ReviewResponse.ReviewListDTO.builder() + .reviewList(dtoList) + .hasNext(reviewSlice.hasNext()) + .nextCursorId(nextCursorId) + .build(); + } } diff --git a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java index 8a5c985..b5b0143 100644 --- a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java +++ b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java @@ -4,11 +4,16 @@ import com.example.umc9th.domain.mission.dto.MissionRequest; import com.example.umc9th.domain.mission.dto.MissionResponse; import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.review.dto.ReviewResponse; +import com.example.umc9th.domain.review.entity.Review; +import com.example.umc9th.domain.review.service.ReviewService; import com.example.umc9th.domain.store.dto.StoreResponse; import com.example.umc9th.domain.store.entity.enums.StoreSortType; import com.example.umc9th.domain.store.service.StoreService; import com.example.umc9th.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -19,6 +24,7 @@ public class StoreController { private final StoreService storeService; + private final ReviewService reviewService; @Operation( summary = "커서 기반 가게 검색", @@ -55,4 +61,18 @@ public ApiResponse addMission( return ApiResponse.onSuccess(response); } + + @GetMapping("/{storeId}/reviews") + @Operation(summary = "가게 리뷰 목록 조회 API", description = "특정 가게의 리뷰 목록을 커서 기반으로 조회합니다.") + @Parameters({ + @Parameter(name = "storeId", description = "가게 ID"), + @Parameter(name = "cursorId", description = "마지막 리뷰 ID (첫 요청 시 비워두세요)") + }) + public ApiResponse getReviewList( + @PathVariable(name = "storeId") Long storeId, + @RequestParam(name = "cursorId", required = false) Long cursorId + ) { + ReviewResponse.ReviewListDTO response = reviewService.getReviewList(storeId, cursorId); + return ApiResponse.onSuccess(response); + } } From 9404395285af9a85756ac4c4a6f1659a9198df94 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Tue, 25 Nov 2025 01:52:35 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[Week9]=20Feat:=20=EA=B0=80=EA=B2=8C=20?= =?UTF-8?q?=EB=AF=B8=EC=85=98=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/controller/MissionController.java | 5 +-- .../mission/converter/MissionConverter.java | 22 +++++++--- .../domain/mission/dto/MissionResponse.java | 12 +++--- .../mission/repository/MissionRepository.java | 17 ++++++++ .../mission/service/MissionService.java | 5 ++- .../mission/service/MissionServiceImpl.java | 42 ++++++++++++++++++- .../store/controller/StoreController.java | 16 +++++++ 7 files changed, 99 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java index e88fe35..914e665 100644 --- a/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java +++ b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java @@ -12,7 +12,6 @@ import org.springframework.web.bind.annotation.RestController; import java.time.LocalDate; -import java.time.LocalDateTime; @RestController @RequestMapping("/api/missions") @@ -23,13 +22,13 @@ public class MissionController { @GetMapping("/my") @Operation(summary = "나의 미션 목록 조회 API", description = "진행중 또는 완료된 미션 목록을 커서 기반으로 조회합니다.") - public ApiResponse getMyMissions( + public ApiResponse getMyMissions( @RequestParam MissionStatus status, @RequestParam(required = false) LocalDate cursorDeadline, @RequestParam(required = false) Long cursorId, @RequestParam(defaultValue = "10") int size ) { - MissionResponse.MyMissionListDTO response = missionService.getMyMissions(1L, status, cursorDeadline, cursorId, size); + MissionResponse.MissionListDTO response = missionService.getMyMissions(1L, status, cursorDeadline, cursorId, size); return ApiResponse.onSuccess(response); } diff --git a/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java b/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java index 047954d..0c36aaf 100644 --- a/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java +++ b/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java @@ -8,15 +8,14 @@ import org.springframework.data.domain.Slice; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; public class MissionConverter { - public static MissionResponse.MyMissionDTO toMyMissionDTO(MissionByMember missionByMember) { + public static MissionResponse.MissionDTO toMyMissionDTO(MissionByMember missionByMember) { Mission mission = missionByMember.getMission(); - return MissionResponse.MyMissionDTO.builder() + return MissionResponse.MissionDTO.builder() .storeName(mission.getStore().getName()) .missionContent(mission.getContent()) .targetAmount(mission.getTargetAmount()) @@ -25,10 +24,10 @@ public static MissionResponse.MyMissionDTO toMyMissionDTO(MissionByMember missio .build(); } - public static MissionResponse.MyMissionListDTO toMyMissionListDTO(Slice missionByMemberSlice) { + public static MissionResponse.MissionListDTO toMyMissionListDTO(Slice missionByMemberSlice) { // 각 MissionByMember 엔터티 -> MyMissionDTO로 변환 -> list로 - List missionDTOList = missionByMemberSlice.getContent().stream() + List missionDTOList = missionByMemberSlice.getContent().stream() .map(MissionConverter::toMyMissionDTO) .collect(Collectors.toList()); @@ -46,7 +45,7 @@ public static MissionResponse.MyMissionListDTO toMyMissionListDTO(Slice MissionDTO 변환 + public static MissionResponse.MissionDTO toMissionDTO(Mission mission) { + return MissionResponse.MissionDTO.builder() + .storeName(mission.getStore().getName()) + .missionContent(mission.getContent()) + .targetAmount(mission.getTargetAmount()) + .rewardPoint(mission.getRewardPoint()) + .deadline(mission.getDeadline()) + .build(); + } } diff --git a/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java index 8c62b5a..cf4223b 100644 --- a/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java +++ b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java @@ -4,10 +4,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.List; public class MissionResponse { @@ -15,8 +13,8 @@ public class MissionResponse { @Getter @Builder @AllArgsConstructor - @Schema(description = "나의 미션 목록의 개별 미션 응답") - public static class MyMissionDTO { + @Schema(description = "미션 목록의 개별 미션 응답") + public static class MissionDTO { private String storeName; private String missionContent; @@ -28,10 +26,10 @@ public static class MyMissionDTO { @Getter @Builder @AllArgsConstructor - @Schema(description = "나의 미션 목록 조회 응답") - public static class MyMissionListDTO { + @Schema(description = "미션 목록 조회 응답") + public static class MissionListDTO { - private List missionList; + private List missionList; private Boolean hasNext; private LocalDate nextCursorDeadline; private Long nextCursorId; diff --git a/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java b/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java index de450fd..5ad529e 100644 --- a/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java +++ b/src/main/java/com/example/umc9th/domain/mission/repository/MissionRepository.java @@ -28,4 +28,21 @@ Slice findAvailableMissionsByRegion( @Param("memberId") Long memberId, @Param("cursorId") Long cursorId, Pageable pageable); + + /** + * 특정 가게의 미션 목록을 커서 기반으로 조회 (미션 ID 기준 내림차순) + * @param storeId 조회할 가게의 ID + * @param cursorId 마지막으로 조회된 미션의 ID (첫 페이지는 null) + * @param pageable LIMIT 절과 페이징 처리를 위한 정보 + * @return Slice + */ + @Query("SELECT m FROM Mission m " + + "WHERE m.store.id = :storeId " + + " AND (:cursorId IS NULL OR m.id < :cursorId) " + // 커서 조건 + "ORDER BY m.id DESC") + Slice findMissionsByStore( + @Param("storeId") Long storeId, + @Param("cursorId") Long cursorId, + Pageable pageable); + } diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java index 45d4509..0610b9e 100644 --- a/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java @@ -4,11 +4,12 @@ import com.example.umc9th.domain.mission.entity.enums.MissionStatus; import java.time.LocalDate; -import java.time.LocalDateTime; public interface MissionService { // 나의 미션 목록 조회 - MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size); + MissionResponse.MissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size); + // 가게 미션 목록 조회 + MissionResponse.MissionListDTO getMissionListByStore(Long storeId, Long cursorId); } diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java index fe6e610..d2bab44 100644 --- a/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java @@ -2,17 +2,21 @@ import com.example.umc9th.domain.mission.converter.MissionConverter; import com.example.umc9th.domain.mission.dto.MissionResponse; +import com.example.umc9th.domain.mission.entity.Mission; import com.example.umc9th.domain.mission.entity.MissionByMember; import com.example.umc9th.domain.mission.entity.enums.MissionStatus; import com.example.umc9th.domain.mission.repository.MissionByMemberRepository; +import com.example.umc9th.domain.mission.repository.MissionRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; -import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; @Service @Transactional(readOnly = true) @@ -20,9 +24,11 @@ public class MissionServiceImpl implements MissionService { private final MissionByMemberRepository missionByMemberRepository; + private static final int PAGE_SIZE = 10; + private final MissionRepository missionRepository; @Override - public MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size) { + public MissionResponse.MissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size) { // 1. Pageable 객체 생성 (커서 기반이므로 페이지 번호는 항상 0) PageRequest pageRequest = PageRequest.of(0, size); @@ -36,4 +42,36 @@ public MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStat return MissionConverter.toMyMissionListDTO(missionSlice); } + @Override + public MissionResponse.MissionListDTO getMissionListByStore(Long storeId, Long cursorId) { + + // 1. Pageable 객체 생성 + Pageable pageable = PageRequest.of(0, PAGE_SIZE); + + // 2. Repository 호출 + Slice missionSlice = missionRepository.findMissionsByStore(storeId, cursorId, pageable); + + // 3. DTO 변환 및 커서 계산 + List missionDTOList = missionSlice.getContent().stream() + .map(MissionConverter::toMissionDTO) + .collect(Collectors.toList()); + + Long nextCursorId = null; + LocalDate nextCursorDeadline = null; + + if (missionSlice.hasNext() && !missionDTOList.isEmpty()) { + // 다음 요청에 사용할 커서 + Mission lastMission = missionSlice.getContent().get(missionDTOList.size() - 1); + nextCursorId = lastMission.getId(); + nextCursorDeadline = lastMission.getDeadline(); + } + + // 4. 응답 DTO 반환 + return MissionResponse.MissionListDTO.builder() + .missionList(missionDTOList) + .hasNext(missionSlice.hasNext()) + .nextCursorDeadline(nextCursorDeadline) + .nextCursorId(nextCursorId) + .build(); + } } diff --git a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java index b5b0143..42a195a 100644 --- a/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java +++ b/src/main/java/com/example/umc9th/domain/store/controller/StoreController.java @@ -4,6 +4,7 @@ import com.example.umc9th.domain.mission.dto.MissionRequest; import com.example.umc9th.domain.mission.dto.MissionResponse; import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.service.MissionService; import com.example.umc9th.domain.review.dto.ReviewResponse; import com.example.umc9th.domain.review.entity.Review; import com.example.umc9th.domain.review.service.ReviewService; @@ -25,6 +26,7 @@ public class StoreController { private final StoreService storeService; private final ReviewService reviewService; + private final MissionService missionService; @Operation( summary = "커서 기반 가게 검색", @@ -75,4 +77,18 @@ public ApiResponse getReviewList( ReviewResponse.ReviewListDTO response = reviewService.getReviewList(storeId, cursorId); return ApiResponse.onSuccess(response); } + + @GetMapping("/{storeId}/missions") + @Operation(summary = "가게 미션 목록 조회 API", description = "특정 가게의 미션 목록을 커서 기반으로 조회합니다.") + @Parameters({ + @Parameter(name = "storeId", description = "가게 ID"), + @Parameter(name = "cursorId", description = "마지막 미션 ID (첫 요청 시 비워두세요)", required = false) + }) + public ApiResponse getMissionListByStore( + @PathVariable(name = "storeId") Long storeId, + @RequestParam(name = "cursorId", required = false) Long cursorId + ) { + MissionResponse.MissionListDTO response = missionService.getMissionListByStore(storeId, cursorId); + return ApiResponse.onSuccess(response); + } } From 7f6803051f622495b5f2de4bf58af70679865b65 Mon Sep 17 00:00:00 2001 From: Yujin1219 Date: Tue, 25 Nov 2025 02:15:23 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[Week9]=20Feat:=20=EC=A7=84=ED=96=89?= =?UTF-8?q?=EC=A4=91=EC=9D=B8=20=EB=AF=B8=EC=85=98=20=EC=A7=84=ED=96=89=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20API=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mission/controller/MissionController.java | 18 ++++++++++---- .../mission/converter/MissionConverter.java | 1 + .../domain/mission/dto/MissionResponse.java | 2 ++ .../mission/entity/MissionByMember.java | 5 ++++ .../mission/service/MissionService.java | 4 ++++ .../mission/service/MissionServiceImpl.java | 24 +++++++++++++++++++ .../apiPayload/code/status/ErrorStatus.java | 5 +++- 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java index 914e665..46806f5 100644 --- a/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java +++ b/src/main/java/com/example/umc9th/domain/mission/controller/MissionController.java @@ -5,11 +5,10 @@ import com.example.umc9th.domain.mission.service.MissionService; import com.example.umc9th.global.apiPayload.ApiResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -32,4 +31,15 @@ public ApiResponse getMyMissions( return ApiResponse.onSuccess(response); } + @PatchMapping("/{mbmId}/complete") + @Operation(summary = "미션 진행 완료 상태 변경 API", description = "진행 중인 미션의 상태를 완료(COMPLETE)로 변경하고 변경 결과를 반환합니다.") + @Parameters({ + @Parameter(name = "mbmId", description = "변경할 MissionByMember의 ID") + }) + public ApiResponse completeMission( + @PathVariable(name = "mbmId") Long mbmId + ) { + MissionResponse.MissionDTO response = missionService.completeMission(mbmId); + return ApiResponse.onSuccess(response); + } } diff --git a/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java b/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java index 0c36aaf..a8e57df 100644 --- a/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java +++ b/src/main/java/com/example/umc9th/domain/mission/converter/MissionConverter.java @@ -21,6 +21,7 @@ public static MissionResponse.MissionDTO toMyMissionDTO(MissionByMember missionB .targetAmount(mission.getTargetAmount()) .rewardPoint(mission.getRewardPoint()) .deadline(mission.getDeadline()) + .missionStatus(missionByMember.getStatus()) .build(); } diff --git a/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java index cf4223b..bc83019 100644 --- a/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java +++ b/src/main/java/com/example/umc9th/domain/mission/dto/MissionResponse.java @@ -1,5 +1,6 @@ package com.example.umc9th.domain.mission.dto; +import com.example.umc9th.domain.mission.entity.enums.MissionStatus; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; @@ -21,6 +22,7 @@ public static class MissionDTO { private Integer rewardPoint; private Integer targetAmount; private LocalDate deadline; + private MissionStatus missionStatus; } @Getter diff --git a/src/main/java/com/example/umc9th/domain/mission/entity/MissionByMember.java b/src/main/java/com/example/umc9th/domain/mission/entity/MissionByMember.java index 758df05..6fed697 100644 --- a/src/main/java/com/example/umc9th/domain/mission/entity/MissionByMember.java +++ b/src/main/java/com/example/umc9th/domain/mission/entity/MissionByMember.java @@ -34,4 +34,9 @@ public class MissionByMember extends BaseEntity { @ColumnDefault("false") private Boolean isReviewed = false; + + // 미션 상태 변경 메소드 + public void updateStatus(MissionStatus status) { + this.status = status; + } } diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java index 0610b9e..4b731c2 100644 --- a/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionService.java @@ -1,6 +1,7 @@ package com.example.umc9th.domain.mission.service; import com.example.umc9th.domain.mission.dto.MissionResponse; +import com.example.umc9th.domain.mission.entity.Mission; import com.example.umc9th.domain.mission.entity.enums.MissionStatus; import java.time.LocalDate; @@ -12,4 +13,7 @@ public interface MissionService { // 가게 미션 목록 조회 MissionResponse.MissionListDTO getMissionListByStore(Long storeId, Long cursorId); + + // 미션 완료 처리 + MissionResponse.MissionDTO completeMission(Long mbmId); } diff --git a/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java index d2bab44..7a63495 100644 --- a/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/mission/service/MissionServiceImpl.java @@ -7,6 +7,8 @@ import com.example.umc9th.domain.mission.entity.enums.MissionStatus; import com.example.umc9th.domain.mission.repository.MissionByMemberRepository; import com.example.umc9th.domain.mission.repository.MissionRepository; +import com.example.umc9th.global.apiPayload.code.status.ErrorStatus; +import com.example.umc9th.global.apiPayload.exception.handler.ErrorHandler; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -74,4 +76,26 @@ public MissionResponse.MissionListDTO getMissionListByStore(Long storeId, Long c .nextCursorId(nextCursorId) .build(); } + + // 미션 진행 완료 처리 + @Transactional + @Override + public MissionResponse.MissionDTO completeMission(Long mbmId) { + + // 1. MissionByMember 엔티티 조회 + MissionByMember mbm = missionByMemberRepository.findById(mbmId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.MISSION_BY_MEMBER_NOT_FOUND)); // 미션-멤버 관계 없음 + + // 2. 현재 상태 확인 (IN_PROGRESS인지 확인) + if (mbm.getStatus() != MissionStatus.IN_PROGRESS) { + throw new ErrorHandler(ErrorStatus.MISSION_STATUS_NOT_IN_PROGRESS); // 진행 중인 미션이 아님 + } + + // 3. 상태 변경 (COMPLETE) + // MissionByMember 엔티티에 updateStatus(MissionStatus status) 메서드가 존재한다고 가정 + mbm.updateStatus(MissionStatus.SUCCESS); + + // 4. 응답 DTO 변환 및 반환 + return MissionConverter.toMyMissionDTO(mbm); + } } diff --git a/src/main/java/com/example/umc9th/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/example/umc9th/global/apiPayload/code/status/ErrorStatus.java index c55e12e..8828af8 100644 --- a/src/main/java/com/example/umc9th/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/example/umc9th/global/apiPayload/code/status/ErrorStatus.java @@ -20,8 +20,11 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER4041", "존재하지 않는 회원입니다."), // 가게 관련 에러 - STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE4041", "존재하지 않는 가게 입니다."); + STORE_NOT_FOUND(HttpStatus.NOT_FOUND, "STORE4041", "존재하지 않는 가게 입니다."), + // 미션 관련 에러 + MISSION_BY_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND,"MISSION4041", "존재하지 않는 유저 미션 입니다."), + MISSION_STATUS_NOT_IN_PROGRESS(HttpStatus.CONFLICT, "MISSION4091","해당 미션은 현재 진행 중(IN_PROGRESS) 상태가 아니므로 완료할 수 없습니다."); private final HttpStatus httpStatus; private final String code; private final String message;