Skip to content
Merged
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 @@ -6,8 +6,6 @@
public record MissionInfo(
Long missionId,
String missionName,
String missionMemo,
int missionOrder,
boolean completed,
String createdAt,
String updatedAt,
Expand All @@ -16,8 +14,6 @@ public static MissionInfo from(Mission mission) {
return new MissionInfo(
mission.getId(),
mission.getName(),
mission.getMemo(),
mission.getMissionOrder(),
mission.isCompleted(),
DateUtil.formatDateTime(mission.getCreatedAt()),
DateUtil.formatDateTime(mission.getUpdatedAt()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
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;
Expand All @@ -29,7 +28,7 @@ public MissionInfo createMission(
return MissionInfo.from(mission);
}

public void updateMissionNameAndMemoIfPresent(
public void updateMissionNameIfPresent(
Long memberId,
Long tripId,
Long stampId,
Expand All @@ -38,14 +37,7 @@ public void updateMissionNameAndMemoIfPresent(
Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId);
Mission mission = missionService.getValidMission(stamp.getId(), missionId);

missionService.updateMissionNameAndMemoIfPresent(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);
missionService.updateMissionNameIfPresent(stamp.getId(), mission, request);
}

public void deleteMission(Long memberId, Long tripId, Long stampId, Long missionId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
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.MissionQueryRepository;
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;
Expand All @@ -28,45 +23,17 @@ public class MissionService {

@Transactional
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());
Mission mission = MissionFactory.create(stamp, request.missionName());

return missionRepository.save(mission);
}

@Transactional
public void updateMissionNameAndMemoIfPresent(
public void updateMissionNameIfPresent(
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);
}
mission.updateName(request.missionName());
}

@Transactional
Expand All @@ -76,16 +43,9 @@ public void deleteMission(Long stampId, Mission 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);
return missionRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId);
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
@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, "이미 존재하는 미션 순서입니다."),
MISSION_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "이미 완료된 미션입니다."),
ALL_MISSIONS_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "모든 미션이 완료되지 않았습니다."),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@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);
public static Mission create(Stamp stamp, String name) {
return Mission.of(stamp, name);
}
}
27 changes: 4 additions & 23 deletions src/main/java/com/ject/studytrip/mission/domain/model/Mission.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,14 @@ public class Mission extends BaseTimeEntity {
@Column(nullable = false)
private String name;

private String memo;

private int missionOrder;

private boolean completed;

public static Mission of(Stamp stamp, String name, String memo, int missionOrder) {
return Mission.builder()
.stamp(stamp)
.name(name)
.memo(memo)
.missionOrder(missionOrder)
.completed(false)
.build();
}

public void update(String name, String memo) {
if (hasText(name)) {
this.name = name;
}
if (hasText(memo)) {
this.memo = memo;
}
public static Mission of(Stamp stamp, String name) {
return Mission.builder().stamp(stamp).name(name).completed(false).build();
}

public void updateMissionOrder(int missionOrder) {
this.missionOrder = missionOrder;
public void updateName(String name) {
if (hasText(name)) this.name = name;
}

public void updateDeletedAt() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,12 @@
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(
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);
Expand All @@ -48,12 +21,6 @@ public static void validateNotDeleted(Mission mission) {
}
}

public static void validateOrderNotDuplicated(boolean exists) {
if (exists) {
throw new CustomException(MissionErrorCode.MISSION_ORDER_ALREADY_EXISTS);
}
}

public static void validateCompleted(Mission mission) {
if (mission.isCompleted())
throw new CustomException(MissionErrorCode.MISSION_ALREADY_COMPLETED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
import java.util.Optional;

public interface MissionRepository {
List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId);

List<Mission> findAllByIdIn(List<Long> ids);

List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(Long stampId);
List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId);

Optional<Mission> findById(Long id);

boolean existsByStampIdAndMissionOrderAndDeletedAtIsNull(Long stampId, int missionOrder);

Mission save(Mission mission);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface MissionJpaRepository extends JpaRepository<Mission, Long> {
List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId);

List<Mission> findAllByIdIn(List<Long> ids);

List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(Long stampId);

boolean existsByStampIdAndMissionOrderAndDeletedAtIsNull(Long stampId, int missionOrder);
List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,21 @@
public class MissionRepositoryAdapter implements MissionRepository {
private final MissionJpaRepository missionJpaRepository;

@Override
public List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId) {
return missionJpaRepository.findAllByStampIdOrderByMissionOrder(stampId);
}

@Override
public List<Mission> findAllByIdIn(List<Long> ids) {
return missionJpaRepository.findAllByIdIn(ids);
}

@Override
public List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(Long stampId) {
return missionJpaRepository.findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(stampId);
public List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(Long stampId) {
return missionJpaRepository.findAllByStampIdAndDeletedAtIsNullOrderByCreatedAt(stampId);
}

@Override
public Optional<Mission> findById(Long id) {
return missionJpaRepository.findById(id);
}

@Override
public boolean existsByStampIdAndMissionOrderAndDeletedAtIsNull(
Long stampId, int missionOrder) {
return missionJpaRepository.existsByStampIdAndMissionOrderAndDeletedAtIsNull(
stampId, missionOrder);
}

@Override
public Mission save(Mission mission) {
return missionJpaRepository.save(mission);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.ject.studytrip.mission.application.dto.MissionInfo;
import com.ject.studytrip.mission.application.facade.MissionFacade;
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.mission.presentation.dto.response.CreateMissionResponse;
import com.ject.studytrip.mission.presentation.dto.response.LoadMissionInfoResponse;
Expand Down Expand Up @@ -44,36 +43,21 @@ public ResponseEntity<StandardResponse> createMission(
HttpStatus.CREATED.value(), CreateMissionResponse.of(result)));
}

@Operation(summary = "미션 수정", description = "특정 미션의 이름 또는 메모를 수정합니다.")
@Operation(summary = "미션 수정", description = "특정 미션의 이름을 수정합니다.")
@PatchMapping("/trips/{tripId}/stamps/{stampId}/missions/{missionId}")
public ResponseEntity<StandardResponse> updateMission(
@AuthenticationPrincipal String memberId,
@PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId,
@PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId,
@PathVariable @NotNull(message = "미션 ID는 필수 요청 파라미터입니다.") Long missionId,
@RequestBody @Valid UpdateMissionRequest request) {
missionFacade.updateMissionNameAndMemoIfPresent(
missionFacade.updateMissionNameIfPresent(
Long.valueOf(memberId), tripId, stampId, missionId, request);

return ResponseEntity.status(HttpStatus.OK)
.body(StandardResponse.success(HttpStatus.OK.value(), null));
}

@Operation(
summary = "미션 순서 변경",
description = "코스형과 탐험형 스탬프 모두 미션 순서를 가지며, 요청된 미션 ID 목록의 순서대로 미션 순서를 변경합니다.")
@PutMapping("/trips/{tripId}/stamps/{stampId}/missions/orders")
public ResponseEntity<StandardResponse> updateMissionOrders(
@AuthenticationPrincipal String memberId,
@PathVariable @NotNull(message = "여행 ID는 필수 요청 파라미터입니다.") Long tripId,
@PathVariable @NotNull(message = "스탬프 ID는 필수 요청 파라미터입니다.") Long stampId,
@RequestBody @Valid UpdateMissionOrderRequest request) {
missionFacade.updateMissionOrders(Long.valueOf(memberId), tripId, stampId, request);

return ResponseEntity.status(HttpStatus.OK)
.body(StandardResponse.success(HttpStatus.OK.value(), null));
}

@Operation(summary = "미션 삭제", description = "특정 미션을 삭제합니다.")
@DeleteMapping("/trips/{tripId}/stamps/{stampId}/missions/{missionId}")
public ResponseEntity<StandardResponse> deleteMission(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.ject.studytrip.mission.presentation.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;

public record CreateMissionRequest(
@Schema(description = "미션 이름") @NotBlank(message = "미션 이름은 필수 요청 값입니다.") String name,
@Schema(description = "미션 메모") String memo,
@Schema(description = "미션 순서") @Min(value = 1, message = "모든 미션 순서는 최소 1 이상이어야 합니다.")
int order) {}
@Schema(description = "미션 이름") @NotBlank(message = "미션 이름은 필수 요청 값입니다.")
String missionName) {}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

public record UpdateMissionRequest(
@Schema(description = "수정할 미션 이름") @NotBlank(message = "새로운 미션 이름은 필수 요청 값입니다.")
String name,
@Schema(description = "수정할 미션 메모") String memo) {}
String missionName) {}
Loading