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 1a97bdf..e88fe35 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 @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.time.LocalDate; import java.time.LocalDateTime; @RestController @@ -24,7 +25,7 @@ public class MissionController { @Operation(summary = "나의 미션 목록 조회 API", description = "진행중 또는 완료된 미션 목록을 커서 기반으로 조회합니다.") public ApiResponse getMyMissions( @RequestParam MissionStatus status, - @RequestParam(required = false) LocalDateTime cursorDeadline, + @RequestParam(required = false) LocalDate cursorDeadline, @RequestParam(required = false) Long cursorId, @RequestParam(defaultValue = "10") int size ) { 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 b5267d7..047954d 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 @@ -1,10 +1,13 @@ package com.example.umc9th.domain.mission.converter; +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.entity.MissionByMember; +import com.example.umc9th.domain.store.entity.Store; import org.springframework.data.domain.Slice; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -30,7 +33,7 @@ public static MissionResponse.MyMissionListDTO toMyMissionListDTO(Slice Entity 변환 + public static Mission toMission(MissionRequest.MissionAddDTO request, Store store) { + return Mission.builder() + .store(store) + .content(request.getContent()) + .deadline(request.getDeadline()) + .targetAmount(request.getTargetAmount()) + .rewardPoint(request.getRewardPoint()) + .build(); + } + + // Entity -> DTO 변환 (생성 응답) + public static MissionResponse.MissionAddResultDTO toMissionAddResultDTO(Mission mission) { + return MissionResponse.MissionAddResultDTO.builder() + .missionId(mission.getId()) + .build(); + } } diff --git a/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequest.java b/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequest.java new file mode 100644 index 0000000..1f62f97 --- /dev/null +++ b/src/main/java/com/example/umc9th/domain/mission/dto/MissionRequest.java @@ -0,0 +1,24 @@ +package com.example.umc9th.domain.mission.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +public class MissionRequest { + + @Getter + @Schema(description = "미션 등록 요청 정보") + public static class MissionAddDTO { + + private String content; + + private LocalDate deadline; + + private Integer targetAmount; + + private Integer rewardPoint; + } +} 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 a11c7fd..8c62b5a 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 @@ -22,7 +22,7 @@ public static class MyMissionDTO { private String missionContent; private Integer rewardPoint; private Integer targetAmount; - private LocalDateTime deadline; + private LocalDate deadline; } @Getter @@ -33,7 +33,15 @@ public static class MyMissionListDTO { private List missionList; private Boolean hasNext; - private LocalDateTime nextCursorDeadline; + private LocalDate nextCursorDeadline; private Long nextCursorId; } + + @Getter + @Builder + @AllArgsConstructor + @Schema(description = "미션 추가 응답") + public static class MissionAddResultDTO { + private Long missionId; + } } diff --git a/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java b/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java index d76e22c..ce29df4 100644 --- a/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java +++ b/src/main/java/com/example/umc9th/domain/mission/entity/Mission.java @@ -4,6 +4,8 @@ import com.example.umc9th.global.apiPayload.code.BaseEntity; import jakarta.persistence.*; import lombok.*; + +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -23,7 +25,7 @@ public class Mission extends BaseEntity { private String content; @Column(nullable = false) - private LocalDateTime deadline; + private LocalDate deadline; @Column(nullable = false) private Integer targetAmount; diff --git a/src/main/java/com/example/umc9th/domain/mission/repository/MissionByMemberRepository.java b/src/main/java/com/example/umc9th/domain/mission/repository/MissionByMemberRepository.java index 9850ae5..424d365 100644 --- a/src/main/java/com/example/umc9th/domain/mission/repository/MissionByMemberRepository.java +++ b/src/main/java/com/example/umc9th/domain/mission/repository/MissionByMemberRepository.java @@ -11,6 +11,7 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -39,7 +40,7 @@ public interface MissionByMemberRepository extends JpaRepository findMyMissionsWithCompoundCursor( @Param("memberId") Long memberId, @Param("status") MissionStatus status, - @Param("cursorDeadline") LocalDateTime cursorDeadline, + @Param("cursorDeadline") LocalDate cursorDeadline, @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 fecece0..45d4509 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 @@ -3,11 +3,12 @@ import com.example.umc9th.domain.mission.dto.MissionResponse; 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, LocalDateTime cursorDeadline, Long cursorId, int size); + MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size); } 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 d831752..fe6e610 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 @@ -11,6 +11,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.time.LocalDateTime; @Service @@ -21,7 +22,7 @@ public class MissionServiceImpl implements MissionService { private final MissionByMemberRepository missionByMemberRepository; @Override - public MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDateTime cursorDeadline, Long cursorId, int size) { + public MissionResponse.MyMissionListDTO getMyMissions(Long memberId, MissionStatus status, LocalDate cursorDeadline, Long cursorId, int size) { // 1. Pageable 객체 생성 (커서 기반이므로 페이지 번호는 항상 0) PageRequest pageRequest = PageRequest.of(0, size); 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 b658052..8a5c985 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 @@ -1,15 +1,17 @@ package com.example.umc9th.domain.store.controller; +import com.example.umc9th.domain.mission.converter.MissionConverter; +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.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 jakarta.validation.Valid; 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.*; @RestController @RequiredArgsConstructor @@ -38,4 +40,19 @@ public ApiResponse searchStores( StoreResponse.StoreListDTO response = storeService.searchStores(region, keyword, sortType, cursorId); return ApiResponse.onSuccess(response); } + + @Operation(summary = "가게에 미션 추가 API", description = "특정 가게(store)에 새로운 미션을 추가합니다.") + @PostMapping("/{storeId}/missions") + public ApiResponse addMission( + @PathVariable(name = "storeId") Long storeId, + @RequestBody @Valid MissionRequest.MissionAddDTO request + ) { + // 3. StoreCommandService의 메소드 호출 + Mission newMission = storeService.addMissionToStore(storeId, request); + + // 4. MissionConverter로 응답 DTO 변환 + MissionResponse.MissionAddResultDTO response = MissionConverter.toMissionAddResultDTO(newMission); + + return ApiResponse.onSuccess(response); + } } diff --git a/src/main/java/com/example/umc9th/domain/store/service/StoreService.java b/src/main/java/com/example/umc9th/domain/store/service/StoreService.java index 3f17b2d..dcc2c32 100644 --- a/src/main/java/com/example/umc9th/domain/store/service/StoreService.java +++ b/src/main/java/com/example/umc9th/domain/store/service/StoreService.java @@ -1,5 +1,7 @@ package com.example.umc9th.domain.store.service; +import com.example.umc9th.domain.mission.dto.MissionRequest; +import com.example.umc9th.domain.mission.entity.Mission; import com.example.umc9th.domain.store.dto.StoreResponse; import com.example.umc9th.domain.store.entity.enums.StoreSortType; @@ -11,4 +13,12 @@ StoreResponse.StoreListDTO searchStores( StoreSortType sortType, Long cursorId ); + + /** + * 가게에 미션을 추가하는 로직 + * @param storeId 가게 ID + * @param request 미션 추가 DTO + * @return 생성된 Mission 엔티티 + */ + Mission addMissionToStore(Long storeId, MissionRequest.MissionAddDTO request); } diff --git a/src/main/java/com/example/umc9th/domain/store/service/StoreServiceImpl.java b/src/main/java/com/example/umc9th/domain/store/service/StoreServiceImpl.java index 477a020..2feefd1 100644 --- a/src/main/java/com/example/umc9th/domain/store/service/StoreServiceImpl.java +++ b/src/main/java/com/example/umc9th/domain/store/service/StoreServiceImpl.java @@ -1,10 +1,17 @@ package com.example.umc9th.domain.store.service; +import com.example.umc9th.domain.mission.converter.MissionConverter; +import com.example.umc9th.domain.mission.dto.MissionRequest; +import com.example.umc9th.domain.mission.entity.Mission; +import com.example.umc9th.domain.mission.repository.MissionRepository; import com.example.umc9th.domain.store.converter.StoreConverter; import com.example.umc9th.domain.store.dto.StoreResponse; import com.example.umc9th.domain.store.entity.Store; import com.example.umc9th.domain.store.entity.enums.StoreSortType; +import com.example.umc9th.domain.store.repository.StoreRepository; import com.example.umc9th.domain.store.repository.StoreRepositoryCustom; +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.Slice; import org.springframework.stereotype.Service; @@ -16,6 +23,8 @@ public class StoreServiceImpl implements StoreService { private final StoreRepositoryCustom storeRepositoryCustom; + private final StoreRepository storeRepository; + private final MissionRepository missionRepository; @Override public StoreResponse.StoreListDTO searchStores( @@ -24,4 +33,19 @@ public StoreResponse.StoreListDTO searchStores( Slice storeSlice = storeRepositoryCustom.searchStores(region, keyword, sortType, cursorId); return StoreConverter.toStoreListDTO(storeSlice); } + + @Transactional + @Override + public Mission addMissionToStore(Long storeId, MissionRequest.MissionAddDTO request) { + + // 1. 가게(Store) 조회 + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new ErrorHandler(ErrorStatus.STORE_NOT_FOUND)); + + // 2. DTO -> Entity 변환 + Mission newMission = MissionConverter.toMission(request, store); + + // 3. Mission 저장 + return missionRepository.save(newMission); + } }