Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member_mission.dto.res.MemberMissionResDTO;
import com.example.umc9th.domain.member_mission.entity.MemberMission;
import com.example.umc9th.domain.mission.dto.res.MissionResDTO;
import com.example.umc9th.domain.mission.entity.Mission;
import org.springframework.data.domain.Page;

Expand Down Expand Up @@ -58,4 +59,33 @@ public static MemberMissionResDTO.ChallengeMissionDTO toChallengeMissionDTO(Memb
.challengedAt(memberMission.getCreatedAt())
.build();
}

// MemberMission 엔티티를 MissionResDTO.MissionPreviewDTO로 변환
public static MissionResDTO.MissionPreviewDTO toMissionPreviewDTO(MemberMission memberMission) {
Mission mission = memberMission.getMission();

return MissionResDTO.MissionPreviewDTO.builder()
.missionId(mission.getId())
.storeName(mission.getStore().getStoreName()) // Mission 엔티티를 통해 Store 이름 가져옴
.point(mission.getPoint())
.createdAt(memberMission.getCreatedAt()) // 미션 수령/시작 시점으로 변경 가능
.build();
}

// Page<MemberMission>을 MissionResDTO.MissionListDTO로 변환
public static MissionResDTO.MissionListDTO toMissionListDTO(Page<MemberMission> memberMissionPage) {

List<MissionResDTO.MissionPreviewDTO> missionPreviewList = memberMissionPage.stream()
.map(MemberMissionConverter::toMissionPreviewDTO)
.collect(Collectors.toList());

return MissionResDTO.MissionListDTO.builder()
.missionList(missionPreviewList)
.isFirst(memberMissionPage.isFirst())
.isLast(memberMissionPage.isLast())
.totalPage(memberMissionPage.getTotalPages())
.totalElements(memberMissionPage.getTotalElements())
.listSize(missionPreviewList.size())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public interface MemberMissionRepository extends JpaRepository<MemberMission, Lo
// "내가(Member) 진행중/진행 완료(isComplete)한 미션" 목록 조회 (페이징 포함)
// 메서드 이름(findAllByMemberAndIsComplete)만으로 쿼리가 생성됩니다.
Page<MemberMission> findAllByMemberAndIsComplete(Member member, Boolean isComplete, Pageable pageable);
Page<MemberMission> findByMemberIdAndIsComplete(Long memberId, Boolean isComplete, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
package com.example.umc9th.domain.mission.controller;

import com.example.umc9th.domain.member_mission.converter.MemberMissionConverter;
import com.example.umc9th.domain.member_mission.entity.MemberMission;
import com.example.umc9th.domain.mission.converter.MissionConverter;
import com.example.umc9th.domain.mission.dto.req.MissionReqDTO;
import com.example.umc9th.domain.mission.dto.res.MissionResDTO;
import com.example.umc9th.domain.mission.entity.Mission;
import com.example.umc9th.domain.mission.service.MissionQueryService;
import com.example.umc9th.domain.mission.service.MissionService;
import com.example.umc9th.domain.mission.service.command.MissionCommandService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.GeneralSuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/regions") // 지역 기반 조회이므로 /regions 경로에 붙이는 것이 자연스러움
public class MissionController {
public class MissionController implements MissionControllerDocs {

private final MissionQueryService missionQueryService;
private final MissionCommandService missionCommandService;
Expand Down Expand Up @@ -51,4 +55,37 @@ public ApiResponse<MissionResDTO.MissionPreviewDTO> addMission(
// 응답 DTO 변환 (MissionConverter에 toMissionPreviewDTO 재활용)
return ApiResponse.of(GeneralSuccessCode.CREATED, MissionConverter.toMissionPreviewDTO(mission));
}
private final MissionService missionService;
// private final MissionConverter missionConverter; // Converter 주입 또는 static 호출

@Override
@GetMapping("/stores/{storeId}")
public ApiResponse<MissionResDTO.MissionListDTO> getStoreMissions(
@PathVariable(name = "storeId") Long storeId,
Pageable pageable
) {
// 1. Service 호출
Page<Mission> missionPage = missionService.getStoreMissions(storeId, pageable);

// 2. Entity(Page<Mission>)를 DTO(MissionListDTO)로 변환
MissionResDTO.MissionListDTO result = MissionConverter.toMissionListDTO(missionPage);

// 3. ApiResponse에 담아 반환
return ApiResponse.onSuccess(result);
}

@GetMapping("/members/{memberId}/missions/in-progress")
public ApiResponse<MissionResDTO.MissionListDTO> getInProgressMissions(
@PathVariable(name = "memberId") Long memberId,
Pageable pageable
) {
// 1. Service 호출 (Page<MemberMission> 반환)
Page<MemberMission> memberMissionPage = missionQueryService.getInProgressMissions(memberId, pageable);

// 2. Converter를 통해 DTO로 변환
MissionResDTO.MissionListDTO result = MemberMissionConverter.toMissionListDTO(memberMissionPage);

// 3. ApiResponse에 담아 반환
return ApiResponse.onSuccess(result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.umc9th.domain.mission.controller;

import com.example.umc9th.domain.mission.dto.res.MissionResDTO;
import com.example.umc9th.global.apiPayload.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

public interface MissionControllerDocs {
@Operation(
summary = "특정 가게의 미션 목록 조회 API",
description = "특정 가게(Store)에 등록된 모든 미션 목록을 페이지네이션으로 조회합니다."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게를 찾을 수 없음")
})
@GetMapping("/stores/{storeId}")
ApiResponse<MissionResDTO.MissionListDTO> getStoreMissions(
@PathVariable(name = "storeId") Long storeId,
Pageable pageable
);
@Operation(
summary = "회원의 진행 중인 미션 목록 조회 API",
description = "특정 회원이 현재 수행 중인 미션 목록(is_complete = false)을 조회합니다."
)
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "회원을 찾을 수 없음")
})
@GetMapping("/members/{memberId}/missions/in-progress")
ApiResponse<MissionResDTO.MissionListDTO> getInProgressMissions(
@PathVariable(name = "memberId") Long memberId,
Pageable pageable
);

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static MissionResDTO.MissionPreviewDTO toMissionPreviewDTO(Mission missio
return MissionResDTO.MissionPreviewDTO.builder()
.missionId(mission.getId())
.storeName(mission.getStore().getStoreName())
.missionCondition(mission.getConditinal()) // 오타 수정 권장 (conditional)
.conditional(mission.getConditinal()) // 오타 수정 권장 (conditional)
.point(mission.getPoint())
.createdAt(mission.getCreatedAt())
.build();
Expand All @@ -35,4 +35,21 @@ public static Mission toMission(MissionReqDTO.AddMissionDTO dto, Store store) {
.store(store) // Service에서 찾은 Store 엔티티 주입
.build();
}

// 2. Page -> MissionListDTO
public static MissionResDTO.MissionListDTO toMissionListDTO(Page<Mission> missionPage) {

List<MissionResDTO.MissionPreviewDTO> missionPreviewList = missionPage.stream()
.map(MissionConverter::toMissionPreviewDTO)
.collect(Collectors.toList());

return MissionResDTO.MissionListDTO.builder()
.missionList(missionPreviewList)
.isFirst(missionPage.isFirst())
.isLast(missionPage.isLast())
.totalPage(missionPage.getTotalPages())
.totalElements(missionPage.getTotalElements())
.listSize(missionPreviewList.size())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.AllArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

public class MissionResDTO {

Expand All @@ -15,9 +16,30 @@ public class MissionResDTO {
@AllArgsConstructor
public static class MissionPreviewDTO {
private Long missionId;

// 첫 번째 정의에서 가져옴
private String storeName; // 미션이 걸려있는 가게 이름
private String missionCondition; // 미션 내용
private Integer point; // 제공 포인트

// 두 번째 정의에서 가져옴 (필드명을 명확하게 정리)
private String missionSpec; // 미션 내용 (이름을 missionSpec으로 통일)
private String conditional; // 미션 조건 (예: 리뷰 3회)
private Integer point; // 제공 포인트

// 첫 번째 정의에서 가져옴
private LocalDateTime createdAt;
}

// 2. 페이징 정보를 포함한 전체 목록 DTO
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MissionListDTO {
List<MissionPreviewDTO> missionList;
Integer listSize;
Integer totalPage;
Long totalElements;
Boolean isFirst;
Boolean isLast;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ public interface MissionRepository extends JpaRepository<Mission, Long> {
// JPQL을 사용해 Mission(m)과 Store(s)를 JOIN합니다.
@Query("SELECT m FROM Mission m JOIN m.store s WHERE s.region = :region")
Page<Mission> findMissionsByRegion(@Param("region") Region region, Pageable pageable);
Page<Mission> findByStoreId(Long storeId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.example.umc9th.domain.mission.service;

import com.example.umc9th.domain.member_mission.entity.MemberMission;
import com.example.umc9th.domain.mission.entity.Mission;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface MissionQueryService {
Page<Mission> getMissionListByRegion(Long regionId, Integer page);
Page<MemberMission> getInProgressMissions(Long memberId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.example.umc9th.domain.mission.service;

import ch.qos.logback.core.status.ErrorStatus;
import com.example.umc9th.domain.member.exception.MemberException;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.domain.member_mission.entity.MemberMission;
import com.example.umc9th.domain.member_mission.repository.MemberMissionRepository;
import com.example.umc9th.domain.mission.entity.Mission;
import com.example.umc9th.domain.mission.repository.MissionRepository;
import com.example.umc9th.domain.region.entity.Region;
Expand All @@ -9,6 +14,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -19,6 +25,8 @@ public class MissionQueryServiceImpl implements MissionQueryService {

private final MissionRepository missionRepository;
private final RegionRepository regionRepository; // Region을 찾기 위해 필요
private final MemberMissionRepository memberMissionRepository;
private final MemberRepository memberRepository; // 회원 검증용

@Override
public Page<Mission> getMissionListByRegion(Long regionId, Integer page) {
Expand All @@ -33,4 +41,13 @@ public Page<Mission> getMissionListByRegion(Long regionId, Integer page) {
// 3. JPQL 메서드 호출
return missionRepository.findMissionsByRegion(region, pageRequest);
}
@Override
public Page<MemberMission> getInProgressMissions(Long memberId, Pageable pageable) {
// 1. Validation: 회원 존재 여부 검증 (권장)
memberRepository.findById(memberId)
.orElseThrow(() -> new GeneralException(GeneralErrorCode.NOT_FOUND));

// 2. Repository 호출: isComplete이 false인 미션 조회
return memberMissionRepository.findByMemberIdAndIsComplete(memberId, false, pageable);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.umc9th.domain.mission.service;

import com.example.umc9th.domain.mission.entity.Mission;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface MissionService {
Page<Mission> getStoreMissions(Long storeId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.umc9th.domain.mission.service;

import ch.qos.logback.core.status.ErrorStatus;
import com.example.umc9th.domain.mission.entity.Mission;
import com.example.umc9th.domain.mission.repository.MissionRepository; // Repository import 필요
// import com.example.umc9th.domain.store.repository.StoreRepository; // Store 검증 시 필요
import com.example.umc9th.domain.store.exception.StoreException;
import com.example.umc9th.domain.store.exception.code.StoreErrorCode;
import com.example.umc9th.domain.store.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service // Spring Bean으로 등록
@RequiredArgsConstructor // Repository 주입을 위해 필요
@Transactional(readOnly = true) // 조회 기능이므로 readOnly = true
public class MissionServiceImpl implements MissionService {

// Repository 주입
private final MissionRepository missionRepository;
private final StoreRepository storeRepository; // Store 검증 시 주입

@Override
public Page<Mission> getStoreMissions(Long storeId, Pageable pageable) {
// 1. Validation: Store 존재 여부 검증 활성화
storeRepository.findById(storeId)
// 💡 StoreErrorCode.NOT_FOUND는 실제 에러 코드 경로로 변경해야 합니다.
.orElseThrow(() -> new StoreException(StoreErrorCode.NOT_FOUND));

// 2. Repository 호출
return missionRepository.findByStoreId(storeId, pageable);
}
}
Loading