diff --git a/Dockerfile b/Dockerfile index 293f04c..b1ed69c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,5 @@ FROM openjdk:21-jdk-slim -RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/* - COPY /build/libs/capstone-0.0.1-SNAPSHOT.jar app.jar -CMD ["java", "-jar", "app.jar"] +CMD ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java b/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java index ed1e31b..0d66b78 100644 --- a/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java +++ b/src/main/java/hyu/erica/capstone/api/code/status/ErrorStatus.java @@ -28,6 +28,7 @@ public enum ErrorStatus implements BaseErrorCode { _TERMS_NOT_AGREED(HttpStatus.FORBIDDEN, "COMMON4013", "이용 약관이 동의되지 않았습니다."), _MEMBER_EMAIL_EXIST(HttpStatus.BAD_REQUEST, "COMMON4014", "이미 가입 된 이메일입니다. 다른 로그인 방식을 이용해주세요."), _RSA_ERROR(HttpStatus.BAD_REQUEST, "COMMON4015", "RSA 에러가 발생했습니다."), + _UNSUPPORTED_PLACE_TYPE(HttpStatus.BAD_REQUEST, "PLACE4000", "지원하지 않는 장소 타입입니다."), // 파일 에러 _FILE_INPUT_ERROR(HttpStatus.BAD_REQUEST, "FILE4000", "파일 입력 중 에러가 발생했습니다."), diff --git a/src/main/java/hyu/erica/capstone/domain/TripPlan.java b/src/main/java/hyu/erica/capstone/domain/TripPlan.java index fd5a7d1..c41bdac 100644 --- a/src/main/java/hyu/erica/capstone/domain/TripPlan.java +++ b/src/main/java/hyu/erica/capstone/domain/TripPlan.java @@ -55,10 +55,14 @@ public class TripPlan { @JoinColumn(name = "user_id") private User user; - @OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL, orphanRemoval = true) + private List tripScheduleItems; + + + @OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL, orphanRemoval = true) private List preferAttractions; - @OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL) + @OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL, orphanRemoval = true) private List preferRestaurants; diff --git a/src/main/java/hyu/erica/capstone/domain/TripScheduleItem.java b/src/main/java/hyu/erica/capstone/domain/TripScheduleItem.java new file mode 100644 index 0000000..cc53b56 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/domain/TripScheduleItem.java @@ -0,0 +1,51 @@ +package hyu.erica.capstone.domain; + +import static jakarta.persistence.EnumType.*; +import static jakarta.persistence.GenerationType.IDENTITY; + +import hyu.erica.capstone.domain.enums.PlaceType; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class TripScheduleItem { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + private int dayNumber; // 1일차, 2일차 등 + + private int orderInDay; // 순서: 1, 2, 3 ... + + private String memo; + + @Enumerated(value = STRING) + private PlaceType placeType; // ATTRACTION, RESTAURANT + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "trip_plan_id") + private TripPlan tripPlan; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "attraction_id") + private Attraction attraction; // nullable + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "restaurant_id") + private Restaurant restaurant; // nullable +} diff --git a/src/main/java/hyu/erica/capstone/domain/enums/PlaceType.java b/src/main/java/hyu/erica/capstone/domain/enums/PlaceType.java new file mode 100644 index 0000000..2d16be3 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/domain/enums/PlaceType.java @@ -0,0 +1,5 @@ +package hyu.erica.capstone.domain.enums; + +public enum PlaceType { + ATTRACTION, RESTAURANT +} diff --git a/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java b/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java index b8c2200..df3a7c1 100644 --- a/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java +++ b/src/main/java/hyu/erica/capstone/domain/mapping/PreferAttraction.java @@ -19,6 +19,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -42,6 +43,7 @@ public class PreferAttraction extends BaseEntity { @JoinColumn(name = "user_id") private User user; + @Setter private boolean isPrefer; diff --git a/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java b/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java index f69b16b..dd25279 100644 --- a/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java +++ b/src/main/java/hyu/erica/capstone/domain/mapping/PreferRestaurant.java @@ -18,6 +18,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; @@ -41,6 +42,7 @@ public class PreferRestaurant extends BaseEntity { @JoinColumn(name = "user_id") private User user; + @Setter private boolean isPrefer; @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) diff --git a/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java b/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java index 75e3e29..1a00e8b 100644 --- a/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java +++ b/src/main/java/hyu/erica/capstone/repository/PreferAttractionRepository.java @@ -2,7 +2,6 @@ import hyu.erica.capstone.domain.mapping.PreferAttraction; import java.util.List; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -10,5 +9,6 @@ public interface PreferAttractionRepository extends JpaRepository { List findAllByTripPlanId(Long tripPlanId); + List findByTripPlanIdAndIsPreferTrue(Long tripPlanId); boolean existsByAttraction_ContentIdAndUserId(Long attractionId, Long userId); } diff --git a/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java b/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java index 098c306..dd441ec 100644 --- a/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java +++ b/src/main/java/hyu/erica/capstone/repository/PreferRestaurantRepository.java @@ -8,5 +8,6 @@ @Repository public interface PreferRestaurantRepository extends JpaRepository { List findAllByTripPlanId(Long tripPlanId); + List findByTripPlanIdAndIsPreferTrue(Long tripPlanId); boolean existsByRestaurantIdAndUserId(Long restaurantId, Long userId); } diff --git a/src/main/java/hyu/erica/capstone/repository/TripScheduleItemRepository.java b/src/main/java/hyu/erica/capstone/repository/TripScheduleItemRepository.java new file mode 100644 index 0000000..d2c1851 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/repository/TripScheduleItemRepository.java @@ -0,0 +1,11 @@ +package hyu.erica.capstone.repository; + +import hyu.erica.capstone.domain.TripScheduleItem; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface TripScheduleItemRepository extends JpaRepository { + List findAllByTripPlanId(Long tripPlanId); +} diff --git a/src/main/java/hyu/erica/capstone/service/async/StyleBackgroundTaskService.java b/src/main/java/hyu/erica/capstone/service/async/StyleBackgroundTaskService.java index 5e78c1a..7069b0d 100644 --- a/src/main/java/hyu/erica/capstone/service/async/StyleBackgroundTaskService.java +++ b/src/main/java/hyu/erica/capstone/service/async/StyleBackgroundTaskService.java @@ -82,6 +82,7 @@ public void handleTripPlanDetails(Long tripPlanId, Style style, User user) { preferAttractionRepository.save(PreferAttraction.builder() .attraction(attraction) .user(managedUser) + .isPrefer(true) .tripPlan(tripPlan) .build()); } diff --git a/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java b/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java index e3345ec..48bef2a 100644 --- a/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java +++ b/src/main/java/hyu/erica/capstone/service/style/impl/StyleCommandServiceImpl.java @@ -86,7 +86,7 @@ public TripPlanResponseDTO submitStyle(Long styleId, Long userId) { .build(); TripPlan saved = tripPlanRepository.save(tripPlan); - entityManager.flush(); + tripPlanRepository.flush(); // 비동기 처리 시작 asyncService.handleTripPlanDetails(saved.getId(), style, user); diff --git a/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanCommandService.java b/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanCommandService.java index 2920336..2b80a40 100644 --- a/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanCommandService.java +++ b/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanCommandService.java @@ -1,6 +1,11 @@ package hyu.erica.capstone.service.tripPlan; +import hyu.erica.capstone.web.dto.trip.request.SaveAttractionRequestDTO; +import hyu.erica.capstone.web.dto.trip.request.SaveRestaurantRequestDTO; + public interface TripPlanCommandService { + Long confirmAttractionRecommendation(Long tripPlanId, SaveAttractionRequestDTO request); + Long confirmRestaurantRecommendation(Long tripPlanId, SaveRestaurantRequestDTO request); } diff --git a/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanQueryService.java b/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanQueryService.java index 5f83505..b42cdc7 100644 --- a/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanQueryService.java +++ b/src/main/java/hyu/erica/capstone/service/tripPlan/TripPlanQueryService.java @@ -1,14 +1,18 @@ package hyu.erica.capstone.service.tripPlan; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionDetailResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionListResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionSearchResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantDetailResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantListResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantSearchResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.TripPlanResultResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionDetailResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionListResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionSearchResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantDetailResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantListResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantSearchResponseDTO; public interface TripPlanQueryService { + // 여행 일정 조회 + TripPlanResultResponseDTO getTripPlan(Long tripPlanId); + // 추천 여행지 리스트 조회 AttractionListResponseDTO getRecommendAttractions(Long tripPlanId); diff --git a/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanCommandServiceImpl.java b/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanCommandServiceImpl.java new file mode 100644 index 0000000..1876b48 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanCommandServiceImpl.java @@ -0,0 +1,143 @@ +package hyu.erica.capstone.service.tripPlan.impl; + +import static java.time.temporal.ChronoUnit.DAYS; + +import hyu.erica.capstone.api.code.status.ErrorStatus; +import hyu.erica.capstone.api.exception.GeneralException; +import hyu.erica.capstone.domain.Attraction; +import hyu.erica.capstone.domain.Restaurant; +import hyu.erica.capstone.domain.TripPlan; +import hyu.erica.capstone.domain.TripScheduleItem; +import hyu.erica.capstone.domain.enums.PlaceType; +import hyu.erica.capstone.domain.mapping.PreferAttraction; +import hyu.erica.capstone.domain.mapping.PreferRestaurant; +import hyu.erica.capstone.repository.PreferAttractionRepository; +import hyu.erica.capstone.repository.PreferRestaurantRepository; +import hyu.erica.capstone.repository.TripPlanRepository; +import hyu.erica.capstone.repository.TripScheduleItemRepository; +import hyu.erica.capstone.service.tripPlan.TripPlanCommandService; +import hyu.erica.capstone.web.dto.trip.request.SaveAttractionRequestDTO; +import hyu.erica.capstone.web.dto.trip.request.SaveRestaurantRequestDTO; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class TripPlanCommandServiceImpl implements TripPlanCommandService { + + private final PreferAttractionRepository preferAttractionRepository; + private final PreferRestaurantRepository preferRestaurantRepository; + private final TripScheduleItemRepository tripScheduleItemRepository; + private final TripPlanRepository tripPlanRepository; + + @Override + public Long confirmAttractionRecommendation(Long tripPlanId, SaveAttractionRequestDTO request) { + List preferAttractions = preferAttractionRepository.findAllByTripPlanId(tripPlanId); + + for (PreferAttraction preferAttraction : preferAttractions) { + if (!request.attractionIds().contains(preferAttraction.getAttraction().getContentId())) { + preferAttraction.setPrefer(false); + } + } + + return tripPlanId; + } + + @Override + public Long confirmRestaurantRecommendation(Long tripPlanId, SaveRestaurantRequestDTO request) { + List preferRestaurants = preferRestaurantRepository.findAllByTripPlanId(tripPlanId); + + for (PreferRestaurant preferRestaurant : preferRestaurants) { + if (!request.restaurantIds().contains(preferRestaurant.getRestaurant().getId())) { + preferRestaurant.setPrefer(false); + } + } + + preferRestaurantRepository.flush(); + + createTripPlanFinal(tripPlanId); + return tripPlanId; + } + private void createTripPlanFinal(Long tripPlanId) { + TripPlan tripPlan = tripPlanRepository.findById(tripPlanId) + .orElseThrow(() -> new GeneralException(ErrorStatus._TRIP_PLAN_NOT_FOUND)); + + List allAttractions = preferAttractionRepository.findByTripPlanIdAndIsPreferTrue(tripPlanId) + .stream().map(PreferAttraction::getAttraction).collect(Collectors.toList()); + + List allRestaurants = preferRestaurantRepository.findByTripPlanIdAndIsPreferTrue(tripPlanId) + .stream().map(PreferRestaurant::getRestaurant).collect(Collectors.toList()); + + int totalDays = (int) DAYS.between(tripPlan.getStartDate(), tripPlan.getEndDate()) + 1; + + // 섞기 + Collections.shuffle(allAttractions); + Collections.shuffle(allRestaurants); + + // 필요한 개수만큼만 사용 (초과할 경우 대비) + int maxAttractions = Math.min(totalDays * 2, allAttractions.size()); + int maxRestaurants = Math.min(totalDays * 2, allRestaurants.size()); + + List usableAttractions = allAttractions.subList(0, maxAttractions); + List usableRestaurants = allRestaurants.subList(0, maxRestaurants); + + List scheduleItems = new ArrayList<>(); + + for (int day = 0; day < totalDays; day++) { + int order = 1; + + // 각 날짜마다 2개씩 할당 + if (day * 2 + 1 < usableAttractions.size()) { + scheduleItems.add(createScheduleItem(tripPlan, day + 1, order++, PlaceType.ATTRACTION, usableAttractions.get(day * 2))); + scheduleItems.add(createScheduleItem(tripPlan, day + 1, order++, PlaceType.RESTAURANT, usableRestaurants.get(day * 2))); + scheduleItems.add(createScheduleItem(tripPlan, day + 1, order++, PlaceType.ATTRACTION, usableAttractions.get(day * 2 + 1))); + scheduleItems.add(createScheduleItem(tripPlan, day + 1, order++, PlaceType.RESTAURANT, usableRestaurants.get(day * 2 + 1))); + } + } + + tripScheduleItemRepository.saveAll(scheduleItems); + } + + private TripScheduleItem createScheduleItem(TripPlan tripPlan, int day, int order, PlaceType type, Object place) { + TripScheduleItem.TripScheduleItemBuilder builder = TripScheduleItem.builder() + .tripPlan(tripPlan) + .dayNumber(day) + .orderInDay(order) + .placeType(type); + + if (type == PlaceType.ATTRACTION) { + builder.attraction((Attraction) place); + } else { + builder.restaurant((Restaurant) place); + } + + return builder.build(); + } + + +// private double distance(double lat1, double lon1, double lat2, double lon2) { +// double R = 6371; // Earth radius in km +// double dLat = Math.toRadians(lat2 - lat1); +// double dLon = Math.toRadians(lon2 - lon1); +// double a = Math.sin(dLat/2) * Math.sin(dLat/2) + +// Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * +// Math.sin(dLon/2) * Math.sin(dLon/2); +// double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); +// return R * c; // in kilometers +// } +// +// +// private T getRandom(List list, Random random) { +// return list.get(random.nextInt(list.size())); +// } + + +} diff --git a/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanQueryServiceImpl.java b/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanQueryServiceImpl.java index 9973baf..b21339e 100644 --- a/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanQueryServiceImpl.java +++ b/src/main/java/hyu/erica/capstone/service/tripPlan/impl/TripPlanQueryServiceImpl.java @@ -5,6 +5,7 @@ import hyu.erica.capstone.domain.Attraction; import hyu.erica.capstone.domain.Restaurant; import hyu.erica.capstone.domain.TripPlan; +import hyu.erica.capstone.domain.TripScheduleItem; import hyu.erica.capstone.domain.mapping.PreferAttraction; import hyu.erica.capstone.domain.mapping.PreferRestaurant; import hyu.erica.capstone.repository.AttractionRepository; @@ -12,14 +13,16 @@ import hyu.erica.capstone.repository.PreferRestaurantRepository; import hyu.erica.capstone.repository.RestaurantRepository; import hyu.erica.capstone.repository.TripPlanRepository; +import hyu.erica.capstone.repository.TripScheduleItemRepository; import hyu.erica.capstone.repository.UserRepository; import hyu.erica.capstone.service.tripPlan.TripPlanQueryService; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionDetailResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionListResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.AttractionSearchResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantDetailResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantListResponseDTO; -import hyu.erica.capstone.web.dto.tripPlan.response.RestaurantSearchResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.TripPlanResultResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionDetailResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionListResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.attraction.AttractionSearchResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantDetailResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantListResponseDTO; +import hyu.erica.capstone.web.dto.tripPlan.response.restaurant.RestaurantSearchResponseDTO; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -35,8 +38,19 @@ public class TripPlanQueryServiceImpl implements TripPlanQueryService { private final RestaurantRepository restaurantRepository; private final PreferRestaurantRepository preferRestaurantRepository; private final PreferAttractionRepository preferAttractionRepository; + private final TripScheduleItemRepository tripScheduleItemRepository; private final UserRepository userRepository; + @Override + public TripPlanResultResponseDTO getTripPlan(Long tripPlanId) { + TripPlan tripPlan = tripPlanRepository.findById(tripPlanId) + .orElseThrow(() -> new GeneralException(ErrorStatus._TRIP_PLAN_NOT_FOUND)); + + List tripScheduleItems = tripScheduleItemRepository.findAllByTripPlanId(tripPlanId); + + return TripPlanResultResponseDTO.of(tripPlan, tripScheduleItems); + } + @Override public AttractionListResponseDTO getRecommendAttractions(Long tripPlanId) { if (!tripPlanRepository.existsById(tripPlanId)) diff --git a/src/main/java/hyu/erica/capstone/web/controller/PlanController.java b/src/main/java/hyu/erica/capstone/web/controller/PlanController.java index 8b0b301..3103141 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/PlanController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/PlanController.java @@ -2,10 +2,15 @@ import hyu.erica.capstone.api.ApiResponse; +import hyu.erica.capstone.api.code.status.SuccessStatus; +import hyu.erica.capstone.service.tripPlan.TripPlanQueryService; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -14,14 +19,28 @@ @Tag(name = "[개발 전] 여행 계획", description = "여행 계획 관련 API") @CrossOrigin @RestController -@RequestMapping("/api/plans") +@RequestMapping("/api/trip-plans") +@RequiredArgsConstructor public class PlanController { + private final TripPlanQueryService tripPlanQueryService; + // 일정 제공 - @GetMapping("/{planId}") - public ApiResponse getPlan() { + @Operation(summary = "일정 제공", description = """ + ### 일정 제공 API + 일정 확인을 위한 API입니다. 조회 하려는 여행 계획 ID를 Path Variable로 입력해주세요. + + ### Path Variables + - tripPlanId: 여행 계획 ID + + ### Response + - tripPlanId: 여행 계획 ID + + """) + @GetMapping("/{tripPlanId}") + public ApiResponse getPlan(@PathVariable Long tripPlanId) { // 일정 제공 - return null; + return ApiResponse.onSuccess(SuccessStatus._OK, tripPlanQueryService.getTripPlan(tripPlanId)); } // Result 화면 위한 API @@ -45,7 +64,6 @@ public ApiResponse editPlan() { return null; } - // 일정 편집 (삭제) @DeleteMapping() public ApiResponse deletePlan() { diff --git a/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java b/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java index 31824e4..7873932 100644 --- a/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java +++ b/src/main/java/hyu/erica/capstone/web/controller/TripPlanController.java @@ -2,6 +2,7 @@ import hyu.erica.capstone.api.ApiResponse; import hyu.erica.capstone.api.code.status.SuccessStatus; +import hyu.erica.capstone.service.tripPlan.TripPlanCommandService; import hyu.erica.capstone.service.tripPlan.TripPlanQueryService; import hyu.erica.capstone.web.dto.trip.request.AdditionalInfoRequestDTO; import hyu.erica.capstone.web.dto.trip.request.PreferActivitiesRequestDTO; @@ -30,6 +31,7 @@ public class TripPlanController { private final TripPlanQueryService tripPlanQueryService; + private final TripPlanCommandService tripPlanCommandService; // 선택지 확인 (여행지) @@ -82,7 +84,7 @@ public ApiResponse searchPlace( } // 여행지 최종 선택 - @Tag(name = "[개발 전] 선택지 확인", description = "선택지 확인 API") + @Tag(name = "선택지 확인", description = "선택지 확인 API") @Operation(summary = "여행지 최종 선택", description = """ ### 여행지 최종 선택 API @@ -95,7 +97,7 @@ public ApiResponse finalPlace( @PathVariable Long tripPlansId, @RequestBody SaveAttractionRequestDTO request) { // 여행지 최종 선택 - return null; + return ApiResponse.onSuccess(SuccessStatus._OK, tripPlanCommandService.confirmAttractionRecommendation(tripPlansId, request)); } // 선택지 확인 (음식점) @@ -151,7 +153,7 @@ public ApiResponse searchRestaurant( // 음식점 최종 선택 - @Tag(name = "[개발 전] 선택지 확인", description = "선택지 확인 API") + @Tag(name = "선택지 확인", description = "선택지 확인 API") @Operation(summary = "음식점 최종 선택", description = """ ### 음식점 최종 선택 API @@ -163,7 +165,7 @@ public ApiResponse finalRestaurant( @PathVariable Long tripPlansId, @RequestBody SaveRestaurantRequestDTO request) { // 음식점 최종 선택 - return null; + return ApiResponse.onSuccess(SuccessStatus._OK, tripPlanCommandService.confirmRestaurantRecommendation(tripPlansId, request)); } diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/TripPlanResultResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/TripPlanResultResponseDTO.java new file mode 100644 index 0000000..4202003 --- /dev/null +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/TripPlanResultResponseDTO.java @@ -0,0 +1,121 @@ +package hyu.erica.capstone.web.dto.tripPlan.response; + +import hyu.erica.capstone.api.code.status.ErrorStatus; +import hyu.erica.capstone.api.exception.GeneralException; +import hyu.erica.capstone.domain.Attraction; +import hyu.erica.capstone.domain.Restaurant; +import hyu.erica.capstone.domain.TripPlan; +import hyu.erica.capstone.domain.TripScheduleItem; +import hyu.erica.capstone.domain.enums.PlaceType; + +import java.time.LocalDate; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public record TripPlanResultResponseDTO( + String title, + String description, + String profileImage, + LocalDate startDate, + LocalDate endDate, + List days +) { + + public static TripPlanResultResponseDTO of(TripPlan tripPlan, List scheduleItems) { + // 일자별로 그룹화 (dayNumber 기준) + Map> groupedByDay = scheduleItems.stream() + .collect(Collectors.groupingBy(TripScheduleItem::getDayNumber)); + + List days = groupedByDay.entrySet().stream() + .map(entry -> { + int dayNumber = entry.getKey(); + List items = entry.getValue().stream() + .sorted(Comparator.comparingInt(TripScheduleItem::getOrderInDay)) // 정렬 + .toList(); + + // startDate + (dayNumber - 1)로 실제 날짜 계산 + LocalDate date = tripPlan.getStartDate().plusDays(dayNumber - 1); + return TripPlanDayDTO.of(dayNumber, date, items); + }) + .sorted(Comparator.comparingInt(TripPlanDayDTO::dayNumber)) // day 순으로 정렬 + .toList(); + + return new TripPlanResultResponseDTO( + tripPlan.getTitle(), + tripPlan.getDescription(), + tripPlan.getProfileImage(), + tripPlan.getStartDate(), + tripPlan.getEndDate(), + days + ); + } + + public record TripPlanDayDTO( + int dayNumber, + LocalDate date, + List scheduleItems + ) { + public static TripPlanDayDTO of(int dayNumber, LocalDate date, List items) { + List dtos = items.stream() + .map(TripScheduleItemDTO::of) + .toList(); + return new TripPlanDayDTO(dayNumber, date, dtos); + } + } + + public record TripScheduleItemDTO( + Long itemId, + int order, + PlaceType placeType, // "ATTRACTION" or "RESTAURANT" + Long placeId, + Double latitude, + Double longitude, + String name, + String imageUrl, + String memo + ) { + public static TripScheduleItemDTO of(TripScheduleItem tripScheduleItem) { + PlaceType placeType = tripScheduleItem.getPlaceType(); + Long placeId; + String name; + String imageUrl; + Double latitude; + Double longitude; + + switch (placeType) { + case ATTRACTION -> { + Attraction attraction = tripScheduleItem.getAttraction(); + placeId = attraction.getContentId(); + name = attraction.getContentName(); + imageUrl = attraction.getImageUrl(); + latitude = attraction.getLatitude(); + longitude = attraction.getLongitude(); + } + case RESTAURANT -> { + Restaurant restaurant = tripScheduleItem.getRestaurant(); + placeId = restaurant.getId(); + name = restaurant.getRestaurantName(); + imageUrl = restaurant.getHomepageUrl(); // 임시 처리 + latitude = restaurant.getLatitude(); + longitude = restaurant.getLongitude(); + } + default -> throw new GeneralException(ErrorStatus._UNSUPPORTED_PLACE_TYPE); + } + + return new TripScheduleItemDTO( + tripScheduleItem.getId(), + tripScheduleItem.getOrderInDay(), + placeType, + placeId, + latitude, + longitude, + name, + imageUrl, + tripScheduleItem.getMemo() + ); + } + } + +} diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionDetailResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionDetailResponseDTO.java similarity index 89% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionDetailResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionDetailResponseDTO.java index 2bccf00..20bf3aa 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionDetailResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionDetailResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.attraction; import hyu.erica.capstone.domain.Attraction; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionListResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionListResponseDTO.java similarity index 89% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionListResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionListResponseDTO.java index da53553..142a685 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionListResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionListResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.attraction; import hyu.erica.capstone.domain.Attraction; import java.util.List; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionResponseDTO.java similarity index 80% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionResponseDTO.java index c270ff5..0dc28d2 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.attraction; public record AttractionResponseDTO(Long attractionId, String name, String imageUrl) { diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionSearchResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionSearchResponseDTO.java similarity index 93% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionSearchResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionSearchResponseDTO.java index 593289a..ac9c163 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/AttractionSearchResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/attraction/AttractionSearchResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.attraction; import hyu.erica.capstone.domain.Attraction; import java.util.ArrayList; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantDetailResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantDetailResponseDTO.java similarity index 89% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantDetailResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantDetailResponseDTO.java index abd7bb0..952dfa5 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantDetailResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantDetailResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.restaurant; import hyu.erica.capstone.domain.Restaurant; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantListResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantListResponseDTO.java similarity index 89% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantListResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantListResponseDTO.java index 0b079a0..c4cb260 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantListResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantListResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.restaurant; import hyu.erica.capstone.domain.Restaurant; import java.util.ArrayList; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantResponseDTO.java similarity index 83% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantResponseDTO.java index ebe2633..95aeb84 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.restaurant; import hyu.erica.capstone.domain.Restaurant; diff --git a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantSearchResponseDTO.java b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantSearchResponseDTO.java similarity index 93% rename from src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantSearchResponseDTO.java rename to src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantSearchResponseDTO.java index 75fda81..4bf332e 100644 --- a/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/RestaurantSearchResponseDTO.java +++ b/src/main/java/hyu/erica/capstone/web/dto/tripPlan/response/restaurant/RestaurantSearchResponseDTO.java @@ -1,4 +1,4 @@ -package hyu.erica.capstone.web.dto.tripPlan.response; +package hyu.erica.capstone.web.dto.tripPlan.response.restaurant; import hyu.erica.capstone.domain.Restaurant; import java.util.ArrayList;