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
Binary file removed src/main/java/com/ject/.DS_Store
Binary file not shown.
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);
}
}
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
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);
}
}
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;
}
}
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);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.ject.studytrip.mission.domain.model;

import static org.springframework.util.StringUtils.hasText;

import com.ject.studytrip.global.common.entity.BaseTimeEntity;
import com.ject.studytrip.stamp.domain.model.Stamp;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import lombok.*;

@Entity
Expand All @@ -23,16 +26,36 @@ 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, int missionOrder) {
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 void updateMissionOrder(int missionOrder) {
this.missionOrder = missionOrder;
}

public void updateDeletedAt() {
this.deletedAt = LocalDateTime.now();
}
}
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(
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@

import com.ject.studytrip.mission.domain.model.Mission;
import java.util.List;
import java.util.Optional;

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

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

List<Mission> findAllByStampIdAndDeletedAtIsNullOrderByMissionOrder(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 @@ -6,4 +6,10 @@

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);
}
Loading