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 @@ -9,6 +9,8 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

@Repository
public interface MemberMissionRepository extends JpaRepository<MemberMission, Long> {
Expand Down Expand Up @@ -49,6 +51,13 @@ Long countChallengingMissionsByRegion(
@Param("region") com.example.demo.global.enums.Region region
);

// 진행 중인 미션 목록 조회
@Query("SELECT mm FROM MemberMission mm " +
"WHERE mm.member.id = :memberId " +
"AND mm.isComplete = false " +
"ORDER BY mm.createdAt DESC")
Page<MemberMission> findChallengingMissions(@Param("memberId") Long memberId, Pageable pageable);

// 중복 도전 체크 (미완료 상태)
boolean existsByMemberAndMissionAndIsCompleteFalse(Member member, Mission mission);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
import com.example.demo.global.apiPayload.code.status.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import com.example.demo.global.validation.annotation.CheckPage;

@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/missions")
Expand Down Expand Up @@ -71,4 +75,44 @@ public ApiResponse<MissionResponseDTO.ChallengeResultDTO> challengeMission(
result
);
}

@GetMapping("/stores/{storeId}/missions")
@Operation(summary = "특정 가게의 미션 목록 조회", description = "특정 가게의 미션 목록을 페이징하여 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "가게를 찾을 수 없음")
})
public ApiResponse<MissionResponseDTO.MissionPreViewListDTO> getStoreMissions(
@Parameter(description = "가게 ID", required = true)
@PathVariable Long storeId,

@Parameter(description = "페이지 번호 (1부터 시작)", required = false)
@RequestParam(defaultValue = "1")
@CheckPage
Integer page
) {
MissionResponseDTO.MissionPreViewListDTO result = missionQueryService.getStoreMissions(storeId, page);
return ApiResponse.of(SuccessStatus.MISSION_OK, result);
}

@GetMapping("/my/challenging")
@Operation(summary = "내가 진행중인 미션 목록 조회", description = "내가 진행중인 미션 목록을 페이징하여 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "회원을 찾을 수 없음")
})
public ApiResponse<MissionResponseDTO.MemberMissionPreViewListDTO> getMyChallengingMissions(
@Parameter(description = "회원 ID", required = true)
@RequestParam Long memberId,

@Parameter(description = "페이지 번호 (1부터 시작)", required = false)
@RequestParam(defaultValue = "1")
@CheckPage
Integer page
) {
MissionResponseDTO.MemberMissionPreViewListDTO result = missionQueryService.getMyChallengingMissions(memberId, page);
return ApiResponse.of(SuccessStatus.MISSION_OK, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,60 @@ public static MissionResponseDTO.ChallengeResultDTO toChallengeResultDTO(MemberM
.startedAt(memberMission.getCreatedAt())
.build();
}

// Page<Mission> -> MissionPreViewListDTO
public static MissionResponseDTO.MissionPreViewListDTO toMissionPreViewListDTO(Page<Mission> missionPage) {
List<MissionResponseDTO.MissionPreViewDTO> missionList = missionPage.getContent().stream()
.map(MissionConverter::toMissionPreViewDTO)
.toList();

return MissionResponseDTO.MissionPreViewListDTO.builder()
.missionList(missionList)
.listSize(missionPage.getSize())
.totalPage(missionPage.getTotalPages())
.totalElements(missionPage.getTotalElements())
.isFirst(missionPage.isFirst())
.isLast(missionPage.isLast())
.build();
}

// Mission -> MissionPreViewDTO
public static MissionResponseDTO.MissionPreViewDTO toMissionPreViewDTO(Mission mission) {
return MissionResponseDTO.MissionPreViewDTO.builder()
.missionId(mission.getId())
.storeName(mission.getStore().getName())
.missionContent(mission.getContent())
.point(mission.getPoint())
.deadline(mission.getDeadline())
.build();
}

// Page<MemberMission> -> MemberMissionPreViewListDTO
public static MissionResponseDTO.MemberMissionPreViewListDTO toMemberMissionPreViewListDTO(Page<MemberMission> memberMissionPage) {
List<MissionResponseDTO.MemberMissionPreViewDTO> missionList = memberMissionPage.getContent().stream()
.map(MissionConverter::toMemberMissionPreViewDTO)
.toList();

return MissionResponseDTO.MemberMissionPreViewListDTO.builder()
.missionList(missionList)
.listSize(memberMissionPage.getSize())
.totalPage(memberMissionPage.getTotalPages())
.totalElements(memberMissionPage.getTotalElements())
.isFirst(memberMissionPage.isFirst())
.isLast(memberMissionPage.isLast())
.build();
}

// MemberMission -> MemberMissionPreViewDTO
public static MissionResponseDTO.MemberMissionPreViewDTO toMemberMissionPreViewDTO(MemberMission memberMission) {
Mission mission = memberMission.getMission();
return MissionResponseDTO.MemberMissionPreViewDTO.builder()
.memberMissionId(memberMission.getId())
.storeName(mission.getStore().getName())
.missionContent(mission.getContent())
.point(mission.getPoint())
.deadline(mission.getDeadline())
.startedAt(memberMission.getCreatedAt())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,55 @@ public static class ChallengeResultDTO {
private LocalDate deadline;
private LocalDateTime startedAt;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MissionPreViewListDTO {
List<MissionPreViewDTO> missionList;
Integer listSize;
Integer totalPage;
Long totalElements;
Boolean isFirst;
Boolean isLast;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MissionPreViewDTO {
Long missionId;
String storeName;
String missionContent;
Integer point;
LocalDate deadline;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MemberMissionPreViewListDTO {
List<MemberMissionPreViewDTO> missionList;
Integer listSize;
Integer totalPage;
Long totalElements;
Boolean isFirst;
Boolean isLast;
}

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MemberMissionPreViewDTO {
Long memberMissionId;
String storeName;
String missionContent;
Integer point;
LocalDate deadline;
LocalDateTime startedAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import com.example.demo.domain.store.entity.Store;

import java.time.LocalDate;

Expand All @@ -34,4 +37,6 @@ Page<Mission> findAvailableMissionsByRegion(
@Param("now") LocalDate now,
Pageable pageable
);

Page<Mission> findAllByStore(Store store, PageRequest pageRequest);
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package com.example.demo.domain.mission.service;

import com.example.demo.domain.member.entity.MemberMission;
import com.example.demo.domain.mission.dto.MissionResponseDTO;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface MissionQueryService {
// 진행중인 미션 조회
Page<MemberMission> getChallengingMissions(Long memberId, Pageable pageable);
Page<MemberMission> getCompletedMissions(Long memberId, Pageable pageable);

// 특정 가게의 미션 목록 조회
MissionResponseDTO.MissionPreViewListDTO getStoreMissions(Long storeId, Integer page);

// 내가 진행중인 미션 목록 조회
MissionResponseDTO.MemberMissionPreViewListDTO getMyChallengingMissions(Long memberId, Integer page);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,58 @@
import com.example.demo.domain.member.entity.MemberMission;
import com.example.demo.domain.member.repository.MemberMissionRepository;
import com.example.demo.domain.member.repository.MemberRepository;
import com.example.demo.domain.mission.converter.MissionConverter;
import com.example.demo.domain.mission.dto.MissionResponseDTO;
import com.example.demo.domain.mission.entity.Mission;
import com.example.demo.domain.mission.repository.MissionRepository;
import com.example.demo.domain.store.entity.Store;
import com.example.demo.domain.store.repository.StoreRepository;
import com.example.demo.global.apiPayload.code.status.ErrorStatus;
import com.example.demo.global.apiPayload.exception.GeneralException;
import com.example.demo.global.enums.Region;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MissionQueryServiceImpl implements MissionQueryService {

private final MemberMissionRepository memberMissionRepository;
private final MissionRepository missionRepository;
private final MemberRepository memberRepository;
private final StoreRepository storeRepository;

@Override
public Page<MemberMission> getChallengingMissions(Long memberId, Pageable pageable) {
// 회원 존재 확인
memberRepository.findById(memberId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND));
public MissionResponseDTO.MissionPreViewListDTO getStoreMissions(Long storeId, Integer page) {
// 1. Store 조회 및 검증
Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new GeneralException(ErrorStatus.STORE_NOT_FOUND));

// 2. 페이징 설정 (페이지는 0부터 시작하므로 -1)
PageRequest pageRequest = PageRequest.of(page - 1, 10);

return memberMissionRepository.findChallengingMissions(memberId, pageable);
// 3. 미션 조회
Page<Mission> missionPage = missionRepository.findAllByStore(store, pageRequest);

// 4. DTO 변환
return MissionConverter.toMissionPreViewListDTO(missionPage);
}

@Override
public Page<MemberMission> getCompletedMissions(Long memberId, Pageable pageable) {
// 회원 존재 확인
memberRepository.findById(memberId)
public MissionResponseDTO.MemberMissionPreViewListDTO getMyChallengingMissions(Long memberId, Integer page) {
// 1. Member 조회 및 검증
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_NOT_FOUND));

return memberMissionRepository.findCompletedMissions(memberId, pageable);
// 2. 페이징 설정 (페이지는 0부터 시작하므로 -1)
PageRequest pageRequest = PageRequest.of(page - 1, 10);

// 3. 진행중인 미션 조회
Page<MemberMission> memberMissionPage = memberMissionRepository.findChallengingMissions(memberId, pageRequest);

// 4. DTO 변환
return MissionConverter.toMemberMissionPreViewListDTO(memberMissionPage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,26 @@
import com.example.demo.domain.review.service.ReviewQueryService;
import com.example.demo.global.apiPayload.ApiResponse;
import com.example.demo.global.apiPayload.code.status.SuccessStatus;
import com.example.demo.global.validation.annotation.CheckPage;
import org.springframework.validation.annotation.Validated;

// Swagger Annotations (springdoc)
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;

// Validation
import jakarta.validation.Valid;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/reviews")
@Tag(name = "리뷰 API", description = "리뷰 관련 API")
@Validated
public class ReviewController {

private final ReviewCommandService reviewCommandService;
Expand All @@ -46,25 +48,21 @@ public ApiResponse<ReviewResponseDTO.CreateResultDTO> createReview(
}

@GetMapping("/my")
@Operation(summary = "내 리뷰 조회", description = "내가 작성한 리뷰를 조회합니다.")
public ApiResponse<ReviewResponseDTO.ReviewListDTO> getMyReviews(
@Parameter(description = "회원 ID", required = true) @RequestParam Long memberId,
@Parameter(description = "가게 ID") @RequestParam(required = false) Long storeId,
@Parameter(description = "최소 별점") @RequestParam(required = false) Float minStar,
@Parameter(description = "최대 별점") @RequestParam(required = false) Float maxStar,
@Parameter(description = "페이지 번호") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "10") int size
@Operation(summary = "내가 작성한 리뷰 목록 조회", description = "내가 작성한 리뷰 목록을 페이징하여 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청")
})
public ApiResponse<ReviewResponseDTO.ReviewPreViewListDTO> getMyReviews(
@Parameter(description = "회원 ID", required = true)
@RequestParam Long memberId,

@Parameter(description = "페이지 번호 (1부터 시작)", required = false)
@RequestParam(defaultValue = "1")
@CheckPage
Integer page
) {
Page<Review> reviews = reviewQueryService.getMyReviews(
memberId,
storeId,
minStar,
maxStar,
PageRequest.of(page, size)
);
return ApiResponse.of(
SuccessStatus.REVIEW_OK,
ReviewConverter.toReviewListDTO(reviews)
);
ReviewResponseDTO.ReviewPreViewListDTO result = reviewQueryService.getMyReviews(memberId, page);
return ApiResponse.of(SuccessStatus.REVIEW_OK, result);
}
}
Loading