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 @@ -20,5 +20,5 @@ public abstract class BaseTimeEntity {

@LastModifiedDate private LocalDateTime updatedAt;

private LocalDateTime deletedAt;
protected LocalDateTime deletedAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.permitAll() // Swagger 경로
.requestMatchers("/api/sample/**", "/api/auth/**")
.permitAll() // 샘플 api 경로
.requestMatchers("/api/trips/categories")
.permitAll()
.anyRequest()
.authenticated()); // 그 외 요청은 모두 인증 수행

Expand Down
28 changes: 28 additions & 0 deletions src/main/java/com/ject/studytrip/global/util/DateUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ject.studytrip.global.util;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateUtil {

private static final DateTimeFormatter DEFAULT_DATE_FORMATTER = DateTimeFormatter.ISO_DATE;
private static final DateTimeFormatter DEFAULT_DATETIME_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

public static String formatDate(LocalDate date) {
return date != null ? date.format(DEFAULT_DATE_FORMATTER) : null;
}

public static LocalDate parseDate(String raw) {
return LocalDate.parse(raw, DEFAULT_DATE_FORMATTER);
}

public static String formatDateTime(LocalDateTime dateTime) {
return dateTime != null ? dateTime.format(DEFAULT_DATETIME_FORMATTER) : null;
}

public static LocalDateTime parseDateTime(String raw) {
return LocalDateTime.parse(raw, DEFAULT_DATETIME_FORMATTER);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.ject.studytrip.stamp.application.dto;

import com.ject.studytrip.global.util.DateUtil;
import com.ject.studytrip.stamp.domain.model.Stamp;

public record StampInfo(
Long stampId,
String stampName,
int stampOrder,
String deadline,
boolean completed,
String createdAt,
String updatedAt,
String deletedAt) {
public static StampInfo from(Stamp stamp) {
return new StampInfo(
stamp.getId(),
stamp.getName(),
stamp.getStampOrder(),
DateUtil.formatDate(stamp.getDeadline()),
stamp.isCompleted(),
DateUtil.formatDateTime(stamp.getCreatedAt()),
DateUtil.formatDateTime(stamp.getUpdatedAt()),
DateUtil.formatDateTime(stamp.getDeletedAt()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.ject.studytrip.stamp.application.service;

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.StampRepository;
import com.ject.studytrip.stamp.presentation.dto.request.CreateStampRequest;
import com.ject.studytrip.trip.domain.model.Trip;
import com.ject.studytrip.trip.domain.model.TripCategory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class StampService {

private final StampRepository stampRepository;

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

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

stampRepository.saveAll(stamps);
}

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

if (newCategory == TripCategory.EXPLORE) {
stamps.forEach(stamp -> stamp.updateStampOrder(0));
return;
}

int order = 1;
for (Stamp stamp : stamps) {
stamp.updateStampOrder(order++);
}
}

public List<Stamp> getStampsByTripId(Long tripId) {
return stampRepository.findAllByTripId(tripId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.ject.studytrip.stamp.domain.error;

import com.ject.studytrip.global.exception.error.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@RequiredArgsConstructor
public enum StampErrorCode implements ErrorCode {
// 400
STAMP_DEADLINE_CANNOT_BE_IN_PAST(HttpStatus.BAD_REQUEST, "스탬프의 마감일은 과거일 수 없습니다."),
STAMP_DEADLINE_EXCEEDS_TRIP_END_DATE(HttpStatus.BAD_REQUEST, "스탬프의 마감일은 여행 종료일을 초과할 수 없습니다."),
INVALID_STAMP_ORDER_FOR_EXPLORATION_TRIP(
HttpStatus.BAD_REQUEST, "탐험형 여행에서는 스탬프 순서를 지정할 수 없으며, 항상 0이여야 합니다. "),
INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP(
HttpStatus.BAD_REQUEST, "코스형 여행의 스탬프 순서의 범위는 최소 1 이상 또는 최대 총 스탬프 개수여야 합니다."),
DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP(HttpStatus.BAD_REQUEST, "코스형 여행의 스탬프 순서에 중복된 값이 존재합니다."),
;

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,14 @@
package com.ject.studytrip.stamp.domain.factory;

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, LocalDate deadline) {
return Stamp.of(trip, name, stampOrder, deadline);
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/ject/studytrip/stamp/domain/model/Stamp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.ject.studytrip.stamp.domain.model;

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

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
@Builder(access = AccessLevel.PRIVATE)
public class Stamp extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "trip_id", nullable = false)
private Trip trip;

@Column(nullable = false)
private String name;

private int stampOrder;

@Column(nullable = false)
private LocalDate deadline;

private boolean completed;

public static Stamp of(Trip trip, String name, int stampOrder, LocalDate deadline) {
return Stamp.builder()
.trip(trip)
.name(name)
.stampOrder(stampOrder)
.deadline(deadline)
.completed(false)
.build();
}

public void updateStampOrder(int newOrder) {
this.stampOrder = newOrder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.ject.studytrip.stamp.domain.policy;

import com.ject.studytrip.global.exception.CustomException;
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;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class StampPolicy {
public static void validateStampDeadline(LocalDate tripEndDate, List<Stamp> stamps) {
if (tripEndDate == null || stamps.isEmpty()) return;

LocalDate today = LocalDate.now();
for (Stamp stamp : stamps) {
LocalDate stampDeadline = stamp.getDeadline();

if (stampDeadline.isBefore(today))
throw new CustomException(StampErrorCode.STAMP_DEADLINE_CANNOT_BE_IN_PAST);

if (stampDeadline.isAfter(tripEndDate))
throw new CustomException(StampErrorCode.STAMP_DEADLINE_EXCEEDS_TRIP_END_DATE);
}
}

public static void validateStampOrders(TripCategory tripCategory, List<Stamp> stamps) {
int maxOrder = stamps.size();
Set<Integer> orderSet = new HashSet<>();

for (Stamp stamp : stamps) {
int order = stamp.getStampOrder();

// 탐험형 여행이면서 순서가 존재할 경우
if (tripCategory == TripCategory.EXPLORE && order > 0)
throw new CustomException(StampErrorCode.INVALID_STAMP_ORDER_FOR_EXPLORATION_TRIP);

if (tripCategory == TripCategory.COURSE) {
// 코스형 여행이면서 순서가 1보다 작거나 총 개수보다 큰 경우
if (order < 1 || order > maxOrder)
throw new CustomException(
StampErrorCode.INVALID_STAMP_ORDER_RANGE_FOR_COURSE_TRIP);

// 코스형 여행이면서 중복된 순서일 경우
if (!orderSet.add(order))
throw new CustomException(StampErrorCode.DUPLICATE_STAMP_ORDER_FOR_COURSE_TRIP);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.ject.studytrip.stamp.domain.repository;

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

public interface StampRepository {
List<Stamp> saveAll(List<Stamp> stamps);

List<Stamp> findAllByTripId(Long tripId);

List<Stamp> findAllByTripIdOrderByDeadlineAsc(Long tripId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ject.studytrip.stamp.infra.jpa;

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

public interface StampJpaRepository extends JpaRepository<Stamp, Long> {
List<Stamp> findAllByTripId(Long tripId);

List<Stamp> findAllByTripIdOrderByDeadlineAsc(Long tripId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.ject.studytrip.stamp.infra.jpa;

import com.ject.studytrip.stamp.domain.model.Stamp;
import com.ject.studytrip.stamp.domain.repository.StampRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class StampRepositoryAdapter implements StampRepository {
private final StampJpaRepository stampJpaRepository;

@Override
public List<Stamp> saveAll(List<Stamp> stamps) {
return stampJpaRepository.saveAll(stamps);
}

@Override
public List<Stamp> findAllByTripId(Long tripId) {
return stampJpaRepository.findAllByTripId(tripId);
}

@Override
public List<Stamp> findAllByTripIdOrderByDeadlineAsc(Long tripId) {
return stampJpaRepository.findAllByTripIdOrderByDeadlineAsc(tripId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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 jakarta.validation.constraints.NotNull;
import java.time.LocalDate;

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

import com.ject.studytrip.stamp.application.dto.StampInfo;
import io.swagger.v3.oas.annotations.media.Schema;

public record LoadStampDetailResponse(
@Schema(description = "스탬프 ID") Long stampId,
@Schema(description = "스탬프 이름") String stampName,
@Schema(description = "스탬프 순서") int stampOrder,
@Schema(description = "스탬프 마감일") String stampDeadline,
@Schema(description = "스탬프 완료 여부") boolean completed) {
public static LoadStampDetailResponse of(StampInfo stampInfo) {
return new LoadStampDetailResponse(
stampInfo.stampId(),
stampInfo.stampName(),
stampInfo.stampOrder(),
stampInfo.deadline(),
stampInfo.completed());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.ject.studytrip.trip.application.dto;

import com.ject.studytrip.trip.domain.model.TripCategory;

public record TripCategoryInfo(String name, String value) {
public static TripCategoryInfo from(TripCategory category) {
return new TripCategoryInfo(category.name(), category.getValue());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.ject.studytrip.trip.application.dto;

import com.ject.studytrip.stamp.application.dto.StampInfo;
import java.util.List;

public record TripDetail(TripInfo tripInfo, List<StampInfo> stampInfos) {
public static TripDetail from(TripInfo tripInfo, List<StampInfo> stampInfos) {
return new TripDetail(tripInfo, stampInfos);
}
}
Loading