Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
Original file line number Diff line number Diff line change
Expand Up @@ -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", "파일 입력 중 에러가 발생했습니다."),
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/hyu/erica/capstone/domain/TripPlan.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TripScheduleItem> tripScheduleItems;


@OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PreferAttraction> preferAttractions;

@OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL)
@OneToMany(mappedBy = "tripPlan", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PreferRestaurant> preferRestaurants;


Expand Down
51 changes: 51 additions & 0 deletions src/main/java/hyu/erica/capstone/domain/TripScheduleItem.java
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions src/main/java/hyu/erica/capstone/domain/enums/PlaceType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package hyu.erica.capstone.domain.enums;

public enum PlaceType {
ATTRACTION, RESTAURANT
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -42,6 +43,7 @@ public class PreferAttraction extends BaseEntity {
@JoinColumn(name = "user_id")
private User user;

@Setter
private boolean isPrefer;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

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;

@Repository
public interface PreferAttractionRepository extends JpaRepository<PreferAttraction, Long> {

List<PreferAttraction> findAllByTripPlanId(Long tripPlanId);
List<PreferAttraction> findByTripPlanIdAndIsPreferTrue(Long tripPlanId);
boolean existsByAttraction_ContentIdAndUserId(Long attractionId, Long userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
@Repository
public interface PreferRestaurantRepository extends JpaRepository<PreferRestaurant, Long> {
List<PreferRestaurant> findAllByTripPlanId(Long tripPlanId);
List<PreferRestaurant> findByTripPlanIdAndIsPreferTrue(Long tripPlanId);
boolean existsByRestaurantIdAndUserId(Long restaurantId, Long userId);
}
Original file line number Diff line number Diff line change
@@ -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<TripScheduleItem, Long> {
List<TripScheduleItem> findAllByTripPlanId(Long tripPlanId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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<PreferAttraction> 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<PreferRestaurant> 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<Attraction> allAttractions = preferAttractionRepository.findByTripPlanIdAndIsPreferTrue(tripPlanId)
.stream().map(PreferAttraction::getAttraction).collect(Collectors.toList());

List<Restaurant> 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<Attraction> usableAttractions = allAttractions.subList(0, maxAttractions);
List<Restaurant> usableRestaurants = allRestaurants.subList(0, maxRestaurants);

List<TripScheduleItem> 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> T getRandom(List<T> list, Random random) {
// return list.get(random.nextInt(list.size()));
// }


}
Loading