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
@@ -0,0 +1,24 @@
package com.ject.studytrip.mission.application.dto;

import com.ject.studytrip.global.util.DateUtil;
import com.ject.studytrip.mission.domain.model.Mission;

public record MissionInfo(
Long missionId,
String missionName,
int missionOrder,
boolean completed,
String createdAt,
String updatedAt,
String deletedAt) {
public static MissionInfo from(Mission mission) {
return new MissionInfo(
mission.getId(),
mission.getName(),
mission.getMissionOrder(),
mission.isCompleted(),
DateUtil.formatDateTime(mission.getCreatedAt()),
DateUtil.formatDateTime(mission.getUpdatedAt()),
DateUtil.formatDateTime(mission.getDeletedAt()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.ject.studytrip.mission.application.service;

import com.ject.studytrip.mission.application.dto.MissionInfo;
import com.ject.studytrip.mission.domain.model.Mission;
import com.ject.studytrip.mission.domain.repository.MissionRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MissionService {
private final MissionRepository missionRepository;

public List<MissionInfo> getMissionsByStamp(Long stampId) {
List<Mission> missions = missionRepository.findAllByStampIdOrderByMissionOrder(stampId);

return missions.stream().map(MissionInfo::from).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ject.studytrip.mission.domain.repository;

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

public interface MissionRepository {
List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ject.studytrip.mission.infra.jpa;

import com.ject.studytrip.mission.domain.model.Mission;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MissionJpaRepository extends JpaRepository<Mission, Long> {
List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ject.studytrip.mission.infra.jpa;

import com.ject.studytrip.mission.domain.model.Mission;
import com.ject.studytrip.mission.domain.repository.MissionRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class MissionRepositoryAdapter implements MissionRepository {
private final MissionJpaRepository missionJpaRepository;

@Override
public List<Mission> findAllByStampIdOrderByMissionOrder(Long stampId) {
return missionJpaRepository.findAllByStampIdOrderByMissionOrder(stampId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ject.studytrip.mission.presentation.dto.response;

import com.ject.studytrip.mission.application.dto.MissionInfo;
import io.swagger.v3.oas.annotations.media.Schema;

public record LoadMissionInfoResponse(
@Schema(description = "미션 ID") Long missionId,
@Schema(description = "미션 이름") String missionName,
@Schema(description = "미션 순서") int missionOrder,
@Schema(description = "미션 완료여부") boolean completed) {
public static LoadMissionInfoResponse of(MissionInfo info) {
return new LoadMissionInfoResponse(
info.missionId(), info.missionName(), info.missionOrder(), info.completed());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ject.studytrip.stamp.application.dto;

import com.ject.studytrip.mission.application.dto.MissionInfo;
import java.util.List;

public record StampDetail(StampInfo stampInfo, List<MissionInfo> missionInfos) {
public static StampDetail from(StampInfo stampInfo, List<MissionInfo> missionInfos) {
return new StampDetail(stampInfo, missionInfos);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.ject.studytrip.stamp.application.facade;

import com.ject.studytrip.mission.application.dto.MissionInfo;
import com.ject.studytrip.mission.application.service.MissionService;
import com.ject.studytrip.stamp.application.dto.StampDetail;
import com.ject.studytrip.stamp.application.dto.StampInfo;
import com.ject.studytrip.stamp.application.service.StampService;
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.stamp.presentation.dto.request.CreateStampRequest;
import com.ject.studytrip.stamp.presentation.dto.request.UpdateStampNameAndDeadlineRequest;
import com.ject.studytrip.stamp.presentation.dto.request.UpdateStampOrderRequest;
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.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class StampFacade {
private final TripService tripService;
private final StampService stampService;
private final MissionService missionService;

@Transactional
public StampInfo createStamp(Long memberId, Long tripId, CreateStampRequest request) {
Trip trip = tripService.getValidTrip(memberId, tripId);
Stamp stamp = stampService.createStamp(trip, request);

tripService.increaseTotalStamps(trip);

return StampInfo.from(stamp);
}

@Transactional
public void updateStampNameAndDeadline(
Long memberId, Long tripId, Long stampId, UpdateStampNameAndDeadlineRequest request) {
Trip trip = tripService.getValidTrip(memberId, tripId);
Stamp stamp = stampService.getValidStamp(trip.getId(), stampId);

stampService.updateStampNameAndDeadline(trip, stamp, request);
}

@Transactional
public void updateStampOrders(Long memberId, Long tripId, UpdateStampOrderRequest request) {
Trip trip = tripService.getValidTrip(memberId, tripId);

stampService.updateStampsOrders(trip, request);
}

@Transactional
public void deleteStamp(Long memberId, Long tripId, Long stampId) {
Trip trip = tripService.getValidTrip(memberId, tripId);
Stamp stamp = stampService.getValidStamp(trip.getId(), stampId);

stampService.deleteStamp(trip.getId(), trip.getCategory(), stamp);
tripService.decreaseTotalStamps(trip);

// TODO : 추후 삭제로직 업데이트 (연쇄 삭제 처리)
}

public List<StampInfo> getStampsByTrip(Long memberId, Long tripId) {
Trip trip = tripService.getValidTrip(memberId, tripId);
List<Stamp> stamps = stampService.getStampsByTripId(trip.getId());

return stamps.stream().map(StampInfo::from).toList();
}

public StampDetail getStamp(Long memberId, Long tripId, Long stampId) {
Trip trip = tripService.getValidTrip(memberId, tripId);
Stamp stamp = stampService.getValidStamp(trip.getId(), stampId);
List<MissionInfo> missionInfos = missionService.getMissionsByStamp(stamp.getId());

return StampDetail.from(StampInfo.from(stamp), missionInfos);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package com.ject.studytrip.stamp.application.service;

import com.ject.studytrip.global.exception.CustomException;
import com.ject.studytrip.stamp.domain.error.StampErrorCode;
import com.ject.studytrip.stamp.domain.factory.StampFactory;
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.stamp.domain.policy.StampPolicy;
import com.ject.studytrip.stamp.domain.repository.StampQueryRepository;
import com.ject.studytrip.stamp.domain.repository.StampRepository;
import com.ject.studytrip.stamp.presentation.dto.request.CreateStampRequest;
import com.ject.studytrip.stamp.presentation.dto.request.UpdateStampNameAndDeadlineRequest;
import com.ject.studytrip.stamp.presentation.dto.request.UpdateStampOrderRequest;
import com.ject.studytrip.trip.domain.model.Trip;
import com.ject.studytrip.trip.domain.model.TripCategory;
import java.util.ArrayList;
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;

Expand All @@ -16,6 +25,23 @@
public class StampService {

private final StampRepository stampRepository;
private final StampQueryRepository stampQueryRepository;

public Stamp createStamp(Trip trip, CreateStampRequest request) {
Stamp newStamp =
StampFactory.create(trip, request.name(), request.order(), request.deadline());

StampPolicy.validateStampDeadline(trip.getEndDate(), List.of(newStamp));

List<Stamp> existingStamps =
stampRepository.findAllByTripIdAndDeletedAtIsNull(trip.getId());
List<Stamp> combinedStamps = new ArrayList<>(existingStamps);
combinedStamps.add(newStamp);

StampPolicy.validateStampOrders(trip.getCategory(), combinedStamps);

return stampRepository.save(newStamp);
}

public void createStamps(Trip trip, List<CreateStampRequest> requests) {
List<Stamp> stamps =
Expand All @@ -35,6 +61,38 @@ public void createStamps(Trip trip, List<CreateStampRequest> requests) {
stampRepository.saveAll(stamps);
}

public void updateStampNameAndDeadline(
Trip trip, Stamp stamp, UpdateStampNameAndDeadlineRequest request) {
stamp.update(request.name(), request.deadline());

StampPolicy.validateStampDeadline(trip.getEndDate(), List.of(stamp));
}

public void updateStampsOrders(Trip trip, UpdateStampOrderRequest request) {
// 배치 조회 (ID 목록 기준으로 조회하지만 순서는 보장되지 않음)
List<Stamp> stamps = stampRepository.findAllByIdIn(request.orderedStampIds());

StampPolicy.validateUpdateStampOrders(
trip.getCategory(), request.orderedStampIds(), stamps);
stamps.forEach(
stamp -> {
StampPolicy.validateStampBelongsToTrip(trip.getId(), stamp);
StampPolicy.validateNotDeleted(stamp);
});

// 조회된 스탬프 ID 를 기준으로 매핑
Map<Long, Stamp> stampMap =
stamps.stream().collect(Collectors.toMap(Stamp::getId, Function.identity()));

// 요청에서 전달된 ID 순서를 기준으로 스탬프 리스트 재정렬
List<Stamp> orderedStamps = request.orderedStampIds().stream().map(stampMap::get).toList();

int newOrder = 1;
for (Stamp stamp : orderedStamps) {
stamp.updateStampOrder(newOrder++);
}
}

public void updateStampsOrderByTripCategoryChange(Long tripId, TripCategory newCategory) {
List<Stamp> stamps = stampRepository.findAllByTripIdOrderByDeadlineAsc(tripId);

Expand All @@ -49,7 +107,36 @@ public void updateStampsOrderByTripCategoryChange(Long tripId, TripCategory newC
}
}

public void deleteStamp(Long tripId, TripCategory tripCategory, Stamp stamp) {
stamp.updateDeletedAt();

if (tripCategory == TripCategory.COURSE) {
shiftStampOrdersAfterDeleted(tripId, stamp.getStampOrder());
}
}

public List<Stamp> getStampsByTripId(Long tripId) {
return stampRepository.findAllByTripId(tripId);
return stampRepository.findAllByTripIdAndDeletedAtIsNull(tripId);
}

public Stamp getValidStamp(Long tripId, Long stampId) {
Stamp stamp =
stampRepository
.findById(stampId)
.orElseThrow(() -> new CustomException(StampErrorCode.STAMP_NOT_FOUND));

StampPolicy.validateStampBelongsToTrip(tripId, stamp);
StampPolicy.validateNotDeleted(stamp);

return stamp;
}

private void shiftStampOrdersAfterDeleted(Long tripId, int deletedStampOrder) {
List<Stamp> affectedStamps =
stampQueryRepository.findStampsToShiftAfterOrder(tripId, deletedStampOrder);

for (Stamp stamp : affectedStamps) {
stamp.updateStampOrder(stamp.getStampOrder() - 1);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ public enum StampErrorCode implements ErrorCode {
INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP(
HttpStatus.BAD_REQUEST, "코스형 여행의 스탬프 순서의 범위는 최소 1 이상 또는 최대 총 스탬프 개수여야 합니다."),
DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP(HttpStatus.BAD_REQUEST, "코스형 여행의 스탬프 순서에 중복된 값이 존재합니다."),
STAMP_ALREADY_DELETED(HttpStatus.BAD_REQUEST, "이미 삭제된 스탬프입니다."),
CANNOT_UPDATE_ORDER_FOR_EXPLORATION_TRIP(HttpStatus.BAD_REQUEST, "탐험형 여행의 스탬프 순서는 변경할 수 없습니다."),
INVALID_STAMP_ID_IN_REQUEST(HttpStatus.BAD_REQUEST, "존재하지 않는 스탬프 ID가 포함되어 있습니다. "),

// 403
STAMP_NOT_BELONG_TO_TRIP(HttpStatus.FORBIDDEN, "해당 스탬프는 요청한 여행에 속하지 않습니다."),

// 404
STAMP_NOT_FOUND(HttpStatus.NOT_FOUND, "요청한 스탬프가 존재하지 않습니다."),
;

private final HttpStatus status;
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/ject/studytrip/stamp/domain/model/Stamp.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.ject.studytrip.stamp.domain.model;

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

import com.ject.studytrip.global.common.entity.BaseTimeEntity;
import com.ject.studytrip.trip.domain.model.Trip;
import jakarta.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Objects;
import lombok.*;

@Entity
Expand Down Expand Up @@ -41,7 +45,16 @@ public static Stamp of(Trip trip, String name, int stampOrder, LocalDate deadlin
.build();
}

public void update(String name, LocalDate deadline) {
if (hasText(name)) this.name = name;
if (Objects.nonNull(deadline)) this.deadline = deadline;
}

public void updateStampOrder(int newOrder) {
this.stampOrder = newOrder;
}

public void updateDeletedAt() {
this.deletedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StampPolicy {
public static void validateStampBelongsToTrip(Long tripId, Stamp stamp) {
if (!stamp.getTrip().getId().equals(tripId))
throw new CustomException(StampErrorCode.STAMP_NOT_BELONG_TO_TRIP);
}

public static void validateNotDeleted(Stamp stamp) {
if (stamp.getDeletedAt() != null)
throw new CustomException(StampErrorCode.STAMP_ALREADY_DELETED);
}

public static void validateStampDeadline(LocalDate tripEndDate, List<Stamp> stamps) {
if (tripEndDate == null || stamps.isEmpty()) return;

Expand Down Expand Up @@ -51,4 +61,13 @@ public static void validateStampOrders(TripCategory tripCategory, List<Stamp> st
}
}
}

public static void validateUpdateStampOrders(
TripCategory tripCategory, List<Long> orderedStampIds, List<Stamp> savedStamps) {
if (tripCategory == TripCategory.EXPLORE && !orderedStampIds.isEmpty())
throw new CustomException(StampErrorCode.CANNOT_UPDATE_ORDER_FOR_EXPLORATION_TRIP);

if (orderedStampIds.size() != savedStamps.size())
throw new CustomException(StampErrorCode.INVALID_STAMP_ID_IN_REQUEST);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ject.studytrip.stamp.domain.repository;

import com.ject.studytrip.stamp.domain.model.Stamp;
import java.util.List;

public interface StampQueryRepository {
List<Stamp> findStampsToShiftAfterOrder(Long tripId, int deletedOrder);
}
Loading