-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 미션 기능 구현(#32) #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
70 changes: 70 additions & 0 deletions
70
src/main/java/com/ject/studytrip/mission/application/facade/MissionFacade.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package com.ject.studytrip.mission.application.facade; | ||
|
|
||
| import com.ject.studytrip.mission.application.dto.MissionInfo; | ||
| import com.ject.studytrip.mission.application.service.MissionService; | ||
| import com.ject.studytrip.mission.domain.model.Mission; | ||
| import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; | ||
| import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionOrderRequest; | ||
| import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; | ||
| import com.ject.studytrip.stamp.application.service.StampService; | ||
| import com.ject.studytrip.stamp.domain.model.Stamp; | ||
| import com.ject.studytrip.trip.application.service.TripService; | ||
| import com.ject.studytrip.trip.domain.model.Trip; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class MissionFacade { | ||
| private final TripService tripService; | ||
| private final StampService stampService; | ||
| private final MissionService missionService; | ||
|
|
||
| public MissionInfo createMission( | ||
| Long memberId, Long tripId, Long stampId, CreateMissionRequest request) { | ||
| Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); | ||
| Mission mission = missionService.createMission(stamp, request); | ||
|
|
||
| return MissionInfo.from(mission); | ||
| } | ||
|
|
||
| public void updateMissionNameAndMemo( | ||
| Long memberId, | ||
| Long tripId, | ||
| Long stampId, | ||
| Long missionId, | ||
| UpdateMissionRequest request) { | ||
| Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); | ||
| Mission mission = missionService.getValidMission(stamp.getId(), missionId); | ||
|
|
||
| missionService.updateMissionNameAndMemo(stamp.getId(), mission, request); | ||
| } | ||
|
|
||
| public void updateMissionOrders( | ||
| Long memberId, Long tripId, Long stampId, UpdateMissionOrderRequest request) { | ||
| Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); | ||
|
|
||
| missionService.updateMissionOrders(stamp.getId(), request); | ||
| } | ||
|
|
||
| public void deleteMission(Long memberId, Long tripId, Long stampId, Long missionId) { | ||
| Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); | ||
| Mission mission = missionService.getValidMission(stamp.getId(), missionId); | ||
|
|
||
| missionService.deleteMission(stamp.getId(), mission); | ||
| } | ||
|
|
||
| public List<MissionInfo> getMissionsByStamp(Long memberId, Long tripId, Long stampId) { | ||
| Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId); | ||
| List<Mission> missions = missionService.getMissionsByStampId(stamp.getId()); | ||
|
|
||
| return missions.stream().map(MissionInfo::from).toList(); | ||
| } | ||
|
|
||
| private Stamp getValidStampFromTripOwnedByMember(Long memberId, Long tripId, Long stampId) { | ||
| Trip trip = tripService.getValidTrip(memberId, tripId); | ||
|
|
||
| return stampService.getValidStamp(trip.getId(), stampId); | ||
| } | ||
| } | ||
85 changes: 85 additions & 0 deletions
85
src/main/java/com/ject/studytrip/mission/application/service/MissionService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,105 @@ | ||
| package com.ject.studytrip.mission.application.service; | ||
|
|
||
| import com.ject.studytrip.global.exception.CustomException; | ||
| import com.ject.studytrip.mission.application.dto.MissionInfo; | ||
| import com.ject.studytrip.mission.domain.error.MissionErrorCode; | ||
| import com.ject.studytrip.mission.domain.factory.MissionFactory; | ||
| import com.ject.studytrip.mission.domain.model.Mission; | ||
| import com.ject.studytrip.mission.domain.policy.MissionPolicy; | ||
| import com.ject.studytrip.mission.domain.repository.MissionRepository; | ||
| import com.ject.studytrip.mission.presentation.dto.request.CreateMissionRequest; | ||
| import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionOrderRequest; | ||
| import com.ject.studytrip.mission.presentation.dto.request.UpdateMissionRequest; | ||
| import com.ject.studytrip.stamp.domain.model.Stamp; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.function.Function; | ||
| import java.util.stream.Collectors; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class MissionService { | ||
| private final MissionRepository missionRepository; | ||
|
|
||
| @Transactional | ||
chaiminwoo0223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| public Mission createMission(Stamp stamp, CreateMissionRequest request) { | ||
| boolean exists = | ||
| missionRepository.existsByStampIdAndMissionOrderAndDeletedAtIsNull( | ||
| stamp.getId(), request.order()); | ||
| MissionPolicy.validateOrderNotDuplicated(exists); | ||
|
|
||
| Mission mission = | ||
| MissionFactory.create(stamp, request.name(), request.memo(), request.order()); | ||
|
|
||
| return missionRepository.save(mission); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updateMissionNameAndMemo( | ||
| Long stampId, Mission mission, UpdateMissionRequest request) { | ||
| validateMissionIsActiveAndBelongsToStamp(stampId, mission); | ||
|
|
||
| mission.update(request.name(), request.memo()); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updateMissionOrders(Long stampId, UpdateMissionOrderRequest request) { | ||
| // 요청된 ID 목록에 해당하는 미션 조회 | ||
| List<Long> orderedMissionIds = request.orderedMissionIds(); | ||
| List<Mission> missions = missionRepository.findAllByIdIn(orderedMissionIds); | ||
|
|
||
| // 정책 검증 | ||
| missions.forEach(mission -> validateMissionIsActiveAndBelongsToStamp(stampId, mission)); | ||
| MissionPolicy.validateMissionOrders(orderedMissionIds, missions); | ||
|
|
||
| // ID -> 미션 맵 생성 | ||
| Map<Long, Mission> missionMap = | ||
| missions.stream().collect(Collectors.toMap(Mission::getId, Function.identity())); | ||
|
|
||
| // 미션 순서 업데이트 | ||
| for (int i = 0; i < orderedMissionIds.size(); i++) { | ||
| Long missionId = orderedMissionIds.get(i); | ||
| Mission mission = missionMap.get(missionId); | ||
| mission.updateMissionOrder(i + 1); | ||
| } | ||
| } | ||
|
|
||
| @Transactional | ||
| public void deleteMission(Long stampId, Mission mission) { | ||
| validateMissionIsActiveAndBelongsToStamp(stampId, mission); | ||
|
|
||
| mission.updateDeletedAt(); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<MissionInfo> getMissionsByStamp(Long stampId) { | ||
| List<Mission> missions = missionRepository.findAllByStampIdOrderByMissionOrder(stampId); | ||
|
|
||
| return missions.stream().map(MissionInfo::from).toList(); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public List<Mission> getMissionsByStampId(Long stampId) { | ||
| return missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(stampId); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public Mission getValidMission(Long stampId, Long missionId) { | ||
| Mission mission = | ||
| missionRepository | ||
| .findById(missionId) | ||
| .orElseThrow(() -> new CustomException(MissionErrorCode.MISSION_NOT_FOUND)); | ||
|
|
||
| validateMissionIsActiveAndBelongsToStamp(stampId, mission); | ||
|
|
||
| return mission; | ||
| } | ||
|
|
||
| private void validateMissionIsActiveAndBelongsToStamp(Long stampId, Mission mission) { | ||
| MissionPolicy.validateMissionBelongsToStamp(stampId, mission); | ||
| MissionPolicy.validateNotDeleted(mission); | ||
| } | ||
| } | ||
40 changes: 40 additions & 0 deletions
40
src/main/java/com/ject/studytrip/mission/domain/error/MissionErrorCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.ject.studytrip.mission.domain.error; | ||
|
|
||
| import com.ject.studytrip.global.exception.error.ErrorCode; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @RequiredArgsConstructor | ||
| public enum MissionErrorCode implements ErrorCode { | ||
| // 400 | ||
| MISSION_ORDER_IDS_DUPLICATED(HttpStatus.BAD_REQUEST, "요청한 미션 ID 목록에 중복이 존재합니다."), | ||
| MISSION_ORDER_SIZE_MISMATCHED(HttpStatus.BAD_REQUEST, "요청한 미션 수의 크기가 기존 미션 수의 크기와 일치하지 않습니다."), | ||
| MISSION_ORDER_IDS_NOT_MATCHED(HttpStatus.BAD_REQUEST, "요청한 미션 ID 목록이 기존 미션 목록과 일치하지 않습니다."), | ||
| MISSION_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "해당 미션은 이미 삭제되었습니다."), | ||
| MISSION_ORDER_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "이미 존재하는 미션 순서입니다."), | ||
|
|
||
| // 403 | ||
| MISSION_NOT_BELONGS_TO_STAMP(HttpStatus.FORBIDDEN, "해당 미션은 요청한 스탬프에 속하지 않습니다."), | ||
|
|
||
| // 404 | ||
| MISSION_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 미션이 존재하지 않습니다."), | ||
| ; | ||
|
|
||
| private final HttpStatus status; | ||
| private final String message; | ||
|
|
||
| @Override | ||
| public String getName() { | ||
| return this.name(); | ||
| } | ||
|
|
||
| @Override | ||
| public HttpStatus getStatus() { | ||
| return this.status; | ||
| } | ||
|
|
||
| @Override | ||
| public String getMessage() { | ||
| return this.message; | ||
| } | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/main/java/com/ject/studytrip/mission/domain/factory/MissionFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.ject.studytrip.mission.domain.factory; | ||
|
|
||
| import com.ject.studytrip.mission.domain.model.Mission; | ||
| import com.ject.studytrip.stamp.domain.model.Stamp; | ||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public class MissionFactory { | ||
| public static Mission create(Stamp stamp, String name, String memo, int missionOrder) { | ||
| return Mission.of(stamp, name, memo, missionOrder); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
src/main/java/com/ject/studytrip/mission/domain/policy/MissionPolicy.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.ject.studytrip.mission.domain.policy; | ||
|
|
||
| import com.ject.studytrip.global.exception.CustomException; | ||
| import com.ject.studytrip.mission.domain.error.MissionErrorCode; | ||
| import com.ject.studytrip.mission.domain.model.Mission; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.stream.Collectors; | ||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public class MissionPolicy { | ||
|
|
||
| public static void validateMissionOrders( | ||
chaiminwoo0223 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| List<Long> orderedMissionIds, List<Mission> savedMissions) { | ||
| // #1: 중복 미션 ID 검증 | ||
| Set<Long> uniqueIds = new HashSet<>(orderedMissionIds); | ||
|
|
||
| if (uniqueIds.size() != orderedMissionIds.size()) { | ||
| throw new CustomException(MissionErrorCode.MISSION_ORDER_IDS_DUPLICATED); | ||
| } | ||
|
|
||
| // #2: 미션 개수 검증 | ||
| if (orderedMissionIds.size() != savedMissions.size()) { | ||
| throw new CustomException(MissionErrorCode.MISSION_ORDER_SIZE_MISMATCHED); | ||
| } | ||
|
|
||
| // #3: 요청 ID와 실제 저장된 미션 ID가 정확히 일치하는지 확인 | ||
| Set<Long> existingIds = | ||
| savedMissions.stream().map(Mission::getId).collect(Collectors.toSet()); | ||
|
|
||
| if (!existingIds.equals(uniqueIds)) { | ||
| throw new CustomException(MissionErrorCode.MISSION_ORDER_IDS_NOT_MATCHED); | ||
| } | ||
| } | ||
|
|
||
| public static void validateMissionBelongsToStamp(Long stampId, Mission mission) { | ||
| if (!mission.getStamp().getId().equals(stampId)) { | ||
| throw new CustomException(MissionErrorCode.MISSION_NOT_BELONGS_TO_STAMP); | ||
| } | ||
| } | ||
|
|
||
| public static void validateNotDeleted(Mission mission) { | ||
| if (mission.getDeletedAt() != null) { | ||
| throw new CustomException(MissionErrorCode.MISSION_ALREADY_DELETED); | ||
| } | ||
| } | ||
|
|
||
| public static void validateOrderNotDuplicated(boolean exists) { | ||
| if (exists) { | ||
| throw new CustomException(MissionErrorCode.MISSION_ORDER_ALREADY_EXISTS); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.