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 @@ -9,6 +9,7 @@
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.UpdateMissionRequest;
import com.ject.studytrip.stamp.application.service.StampCommandService;
import com.ject.studytrip.stamp.application.service.StampQueryService;
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.trip.application.service.TripQueryService;
Expand All @@ -28,6 +29,7 @@ public class MissionFacade {
private final StampQueryService stampQueryService;
private final MissionQueryService missionQueryService;

private final StampCommandService stampCommandService;
private final MissionCommandService missionCommandService;

@Caching(
Expand All @@ -47,6 +49,7 @@ public MissionInfo createMission(
Stamp stamp = getValidStampFromTripOwnedByMember(memberId, tripId, stampId);
Mission mission = missionCommandService.createMission(stamp, request);

stampCommandService.increaseTotalMissions(stamp);
return MissionInfo.from(mission);
}

Expand Down Expand Up @@ -91,6 +94,7 @@ public void deleteMission(Long memberId, Long tripId, Long stampId, Long mission
Mission mission = missionQueryService.getValidMission(stamp.getId(), missionId);

missionCommandService.deleteMission(mission);
stampCommandService.decreaseTotalMissions(stamp);
}

@Cacheable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,31 @@ public class DailyMissionQueryService {
public List<DailyMission> getValidDailyMissionsByIds(
Long dailyGoalId, List<Long> dailyMissionIds) {
List<DailyMission> dailyMissions = dailyMissionRepository.findAllByIdIn(dailyMissionIds);
validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId);

DailyMissionPolicy.validateExistAll(dailyMissions, dailyMissionIds);
dailyMissions.forEach(
dailyMission -> {
DailyMissionPolicy.validateBelongsToDailyGoal(dailyMission, dailyGoalId);
DailyMissionPolicy.validateNotDeleted(dailyMission);
});
return dailyMissions;
}

public List<DailyMission> getValidDailyMissionsWithMissionAndStampByIds(
Long dailyGoalId, List<Long> dailyMissionIds) {
List<DailyMission> dailyMissions =
dailyMissionQueryRepository.findAllWithMissionAndStampByIds(dailyMissionIds);
validateDailyMissions(dailyMissions, dailyMissionIds, dailyGoalId);

return dailyMissions;
}

public List<DailyMission> getDailyMissionsByDailyGoal(Long dailyGoalId) {
return dailyMissionQueryRepository.findAllByDailyGoalIdFetchJoinMission(dailyGoalId);
}

private void validateDailyMissions(
List<DailyMission> dailyMissions, List<Long> dailyMissionIds, Long dailyGoalId) {
DailyMissionPolicy.validateExistAll(dailyMissions, dailyMissionIds);
dailyMissions.forEach(
dailyMission -> {
DailyMissionPolicy.validateBelongsToDailyGoal(dailyMission, dailyGoalId);
DailyMissionPolicy.validateNotDeleted(dailyMission);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
public interface DailyMissionQueryRepository {
List<DailyMission> findAllByDailyGoalIdFetchJoinMission(Long dailyGoalId);

List<DailyMission> findAllWithMissionAndStampByIds(List<Long> ids);

long deleteAllByDeletedAtIsNotNull();

long deleteAllByDeletedMissionOwner();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ject.studytrip.mission.domain.model.QDailyMission;
import com.ject.studytrip.mission.domain.model.QMission;
import com.ject.studytrip.mission.domain.repository.DailyMissionQueryRepository;
import com.ject.studytrip.stamp.domain.model.QStamp;
import com.ject.studytrip.trip.domain.model.QDailyGoal;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
Expand All @@ -16,6 +17,7 @@
public class DailyMissionQueryRepositoryAdapter implements DailyMissionQueryRepository {
private final JPAQueryFactory queryFactory;
private final QDailyMission dailyMission = QDailyMission.dailyMission;
private final QStamp stamp = QStamp.stamp;
private final QMission mission = QMission.mission;
private final QDailyGoal dailyGoal = QDailyGoal.dailyGoal;

Expand All @@ -29,6 +31,18 @@ public List<DailyMission> findAllByDailyGoalIdFetchJoinMission(Long dailyGoalId)
.fetch();
}

@Override
public List<DailyMission> findAllWithMissionAndStampByIds(List<Long> ids) {
return queryFactory
.selectFrom(dailyMission)
.join(dailyMission.mission, mission)
.fetchJoin()
.join(mission.stamp, stamp)
.fetchJoin()
.where(dailyMission.id.in(ids))
.fetch();
}

@Override
public long deleteAllByDeletedAtIsNotNull() {
return queryFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ public record StampInfo(
Long stampId,
String stampName,
int stampOrder,
String endDate,
int totalMissions,
int completedMissions,
boolean completed,
String createdAt,
String updatedAt,
Expand All @@ -16,6 +19,9 @@ public static StampInfo from(Stamp stamp) {
stamp.getId(),
stamp.getName(),
stamp.getStampOrder(),
DateUtil.formatDate(stamp.getEndDate()),
stamp.getTotalMissions(),
stamp.getCompletedMissions(),
stamp.isCompleted(),
DateUtil.formatDateTime(stamp.getCreatedAt()),
DateUtil.formatDateTime(stamp.getUpdatedAt()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void updateStamp(Long memberId, Long tripId, Long stampId, UpdateStampReq
Trip trip = tripQueryService.getValidTrip(memberId, tripId);
Stamp stamp = stampQueryService.getValidStamp(trip.getId(), stampId);

stampCommandService.updateStampName(stamp, request);
stampCommandService.updateStamp(trip, stamp, request);
}

@Caching(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.ject.studytrip.stamp.presentation.dto.request.UpdateStampRequest;
import com.ject.studytrip.trip.domain.model.Trip;
import com.ject.studytrip.trip.domain.model.TripCategory;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -23,31 +24,40 @@ public class StampCommandService {
private final StampQueryRepository stampQueryRepository;

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

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

StampPolicy.validateStampOrders(trip.getCategory(), combinedStamps);
StampPolicy.validateEndDate(trip.getEndDate(), newStamp.getEndDate());

return stampRepository.save(newStamp);
}

public void createStamps(Trip trip, List<CreateStampRequest> requests) {
List<Stamp> stamps =
requests.stream()
.map(stamp -> StampFactory.create(trip, stamp.name(), stamp.order()))
.map(
stamp ->
StampFactory.create(
trip, stamp.name(), stamp.order(), stamp.endDate()))
.toList();

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

stampRepository.saveAll(stamps);
}

public void updateStampName(Stamp stamp, UpdateStampRequest request) {
public void updateStamp(Trip trip, Stamp stamp, UpdateStampRequest request) {
stamp.updateName(request.name());

LocalDate endDate = request.endDate();
StampPolicy.validateEndDate(trip.getEndDate(), endDate);
stamp.updateEndDate(endDate);
}

public void updateStampOrders(Trip trip, UpdateStampOrderRequest request) {
Expand Down Expand Up @@ -129,4 +139,16 @@ private void shiftStampOrdersAfterDeleted(Long tripId, int deletedStampOrder) {
stamp.updateStampOrder(stamp.getStampOrder() - 1);
}
}

public void increaseTotalMissions(Stamp stamp) {
stamp.increaseTotalMissions();
}

public void decreaseTotalMissions(Stamp stamp) {
stamp.decreaseTotalMissions();
}

public void increaseCompletedMissions(Stamp stamp, int count) {
stamp.increaseCompletedMissions(count);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public enum StampErrorCode implements ErrorCode {
STAMP_LIST_CANNOT_BE_EMPTY(HttpStatus.BAD_REQUEST, "스탬프 목록은 비어있을 수 없습니다."),
STAMP_ALREADY_COMPLETED(HttpStatus.BAD_REQUEST, "이미 완료된 스탬프입니다."),
ALL_STAMPS_NOT_COMPLETED(HttpStatus.BAD_REQUEST, "모든 스탬프가 완료되지 않았습니다."),
STAMP_END_DATE_CANNOT_BE_IN_PAST(HttpStatus.BAD_REQUEST, "스탬프의 종료일은 과거일 수 없습니다."),
STAMP_END_DATE_AFTER_TRIP_END_DATE_NOT_ALLOWED(
HttpStatus.BAD_REQUEST, "스탬프의 종료일은 여행 종료일보다 이후일 수 없습니다."),

// 403
STAMP_NOT_BELONG_TO_TRIP(HttpStatus.FORBIDDEN, "해당 스탬프는 요청한 여행에 속하지 않습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.trip.domain.model.Trip;
import java.time.LocalDate;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StampFactory {
public static Stamp create(Trip trip, String name, int stampOrder) {
return Stamp.of(trip, name, stampOrder);
public static Stamp create(Trip trip, String name, int stampOrder, LocalDate endDate) {
return Stamp.of(trip, name, stampOrder, endDate);
}
}
28 changes: 27 additions & 1 deletion src/main/java/com/ject/studytrip/stamp/domain/model/Stamp.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 lombok.*;

Expand All @@ -28,13 +29,22 @@ public class Stamp extends BaseTimeEntity {

private int stampOrder;

private LocalDate endDate;

private int totalMissions;

private int completedMissions;

private boolean completed;

public static Stamp of(Trip trip, String name, int stampOrder) {
public static Stamp of(Trip trip, String name, int stampOrder, LocalDate endDate) {
return Stamp.builder()
.trip(trip)
.name(name)
.stampOrder(stampOrder)
.endDate(endDate)
.totalMissions(0)
.completedMissions(0)
.completed(false)
.build();
}
Expand All @@ -47,11 +57,27 @@ public void updateStampOrder(int newOrder) {
this.stampOrder = newOrder;
}

public void updateEndDate(LocalDate endDate) {
this.endDate = endDate;
}

public void updateCompleted() {
this.completed = true;
}

public void updateDeletedAt() {
this.deletedAt = LocalDateTime.now();
}

public void increaseTotalMissions() {
this.totalMissions += 1;
}

public void decreaseTotalMissions() {
this.totalMissions -= 1;
}

public void increaseCompletedMissions(int count) {
this.completedMissions += count;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.ject.studytrip.stamp.domain.error.StampErrorCode;
import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.trip.domain.model.TripCategory;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -72,4 +73,22 @@ public static void validateAllCompleted(boolean exists) {
throw new CustomException(StampErrorCode.ALL_STAMPS_NOT_COMPLETED);
}
}

public static void validateEndDate(LocalDate tripEndDate, LocalDate stampEndDate) {
if (stampEndDate == null) return;

// 스탬프 종료일이 과거일 경우
LocalDate today = LocalDate.now();
if (stampEndDate.isBefore(today)) {
throw new CustomException(StampErrorCode.STAMP_END_DATE_CANNOT_BE_IN_PAST);
}

if (tripEndDate == null) return;

// 스탬프 종료일이 여행 종료일 이후일 경우
if (stampEndDate.isAfter(tripEndDate)) {
throw new CustomException(
StampErrorCode.STAMP_END_DATE_AFTER_TRIP_END_DATE_NOT_ALLOWED);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,15 @@ public ResponseEntity<StandardResponse> createStamp(
HttpStatus.CREATED.value(), CreateStampResponse.of(result)));
}

@Operation(summary = "스탬프 수정", description = "특정 스탬프의 이름을 수정합니다.")
@Operation(
summary = "스탬프 수정",
description =
"""
특정 스탬프의 이름과 종료일을 수정합니다.

- 이름과 종료일 중 변경하지 않는 필드는 요청 바디에서 생략해도 됩니다.
- 종료일을 '없음'으로 변경하려면 `endDate: null`로 명시적으로 전달해야 합니다.
""")
@PatchMapping("/{tripId}/stamps/{stampId}")
public ResponseEntity<StandardResponse> updateStamp(
@AuthenticationPrincipal String memberId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.ject.studytrip.stamp.presentation.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.FutureOrPresent;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import java.time.LocalDate;

public record CreateStampRequest(
@Schema(description = "스탬프 이름") @NotEmpty(message = "스탬프 이름은 필수 요청 값입니다.") String name,
@Schema(description = "스탬프 순서") @Min(value = 0, message = "스탬프 순서는 최소 0 이상이여야 합니다.")
int order) {}
int order,
@Schema(description = "스탬프 종료일") @FutureOrPresent(message = "스탬프 종료일은 현재 날짜보다 과거일 수 없습니다.")
LocalDate endDate) {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.ject.studytrip.stamp.presentation.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.FutureOrPresent;
import java.time.LocalDate;

public record UpdateStampRequest(@Schema(description = "수정할 스탬프 이름") String name) {}
public record UpdateStampRequest(
@Schema(description = "수정할 스탬프 이름") String name,
@Schema(description = "수정할 스탬프 종료일")
@FutureOrPresent(message = "스탬프 종료일은 현재 날짜보다 과거일 수 없습니다.")
LocalDate endDate) {}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ public record LoadStampDetailResponse(
@Schema(description = "스탬프 ID") Long stampId,
@Schema(description = "스탬프 이름") String stampName,
@Schema(description = "스탬프 순서") int stampOrder,
@Schema(description = "스탬프 종료일") String endDate,
@Schema(description = "스탬프에 속한 총 미션 수") int totalMissions,
@Schema(description = "스탬프에 속한 완료된 미션 수") int completedMissions,
@Schema(description = "스탬프 완료 여부") boolean completed,
@Schema(description = "미션 목록") List<LoadMissionInfoResponse> missions) {
public static LoadStampDetailResponse of(StampInfo stampInfo, List<MissionInfo> missionInfos) {
return new LoadStampDetailResponse(
stampInfo.stampId(),
stampInfo.stampName(),
stampInfo.stampOrder(),
stampInfo.endDate(),
stampInfo.totalMissions(),
stampInfo.completedMissions(),
stampInfo.completed(),
missionInfos.stream().map(LoadMissionInfoResponse::of).toList());
}
Expand Down
Loading