Skip to content

Commit 900e272

Browse files
authored
Merge pull request #23 from TripTalk/feat/#22
[#22] Feat: 여행 일정 저장 API 구현
2 parents 80ab4eb + 28b7646 commit 900e272

File tree

10 files changed

+390
-16
lines changed

10 files changed

+390
-16
lines changed

src/main/java/com/example/triptalk/domain/tripPlan/controller/TripPlanController.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,31 @@ public ApiResponse<TripPlanResponse.TripPlanStatusDTO> markTripPlanAsTraveled(
5858
TripPlanResponse.TripPlanStatusDTO response = tripPlanService.changeTripPlanStatusToTraveled(tripPlanId, userId);
5959
return ApiResponse.onSuccess(response);
6060
}
61+
62+
@PostMapping("/from-fastapi")
63+
@Operation(
64+
summary = "FastAPI 생성 여행 계획 저장",
65+
description = """
66+
**FastAPI에서 생성된 여행 계획을 DB에 저장합니다.**
67+
68+
### 📝 저장 데이터
69+
- 여행 기본 정보 (제목, 출발지, 목적지, 날짜, 예산 등)
70+
- 하이라이트 목록
71+
- 일별 상세 일정 (DailySchedule + ScheduleItem)
72+
- 교통편 정보 (출발편, 귀환편)
73+
- 숙소 정보
74+
75+
### 🔐 인증
76+
- Authorization 헤더에 Bearer 토큰 필요
77+
- 로그인한 사용자의 여행 계획으로 저장
78+
"""
79+
)
80+
public ApiResponse<TripPlanResponse.TripPlanDTO> createTripPlanFromFastAPI(
81+
@RequestBody com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO request,
82+
HttpServletRequest httpRequest
83+
) {
84+
Long userId = authUtil.getUserIdFromRequest(httpRequest);
85+
TripPlanResponse.TripPlanDTO response = tripPlanService.createTripPlanFromFastAPI(userId, request);
86+
return ApiResponse.onSuccess(response);
87+
}
6188
}

src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private static List<TripPlanResponse.TransportationDTO> toTransportationDTO(List
5151
.map(t -> TripPlanResponse.TransportationDTO.builder()
5252
.origin(t.getOrigin())
5353
.destination(t.getDestination())
54-
.name(t.getName())
54+
.name(t.getAirlineName())
5555
.price(t.getPrice())
5656
.build())
5757
.toList();
@@ -144,4 +144,150 @@ public static TripPlanResponse.TripPlanStatusDTO toTripPlanStatusDTO(TripPlan tr
144144
.status(tripPlan.getStatus().name())
145145
.build();
146146
}
147+
148+
// ========== FastAPI 데이터 변환 메서드 ==========
149+
150+
/**
151+
* FastAPI 요청 DTO를 TripPlan 엔티티로 변환
152+
*/
153+
public static TripPlan toTripPlanEntity(
154+
com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO request,
155+
com.example.triptalk.domain.user.entity.User user
156+
) {
157+
// TravelStyles 한글 문자열을 Enum으로 변환
158+
Set<TravelStyle> travelStyleSet = new HashSet<>();
159+
if (request.getTravelStyles() != null) {
160+
for (String styleStr : request.getTravelStyles()) {
161+
TravelStyle style = mapKoreanToTravelStyle(styleStr);
162+
if (style != null) {
163+
travelStyleSet.add(style);
164+
}
165+
}
166+
}
167+
168+
return TripPlan.builder()
169+
.title(request.getTitle())
170+
.destination(request.getDestination())
171+
.departure(request.getDeparture())
172+
.startDate(request.getStartDate())
173+
.endDate(request.getEndDate())
174+
.companions(request.getCompanions())
175+
.budget(request.getBudget())
176+
.travelStyles(travelStyleSet)
177+
.imgUrl("https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=800")
178+
.status(com.example.triptalk.domain.tripPlan.enums.TripStatus.PLANNED)
179+
.user(user)
180+
.build();
181+
}
182+
183+
/**
184+
* 한글 문자열을 TravelStyle Enum으로 매핑
185+
*/
186+
private static TravelStyle mapKoreanToTravelStyle(String korean) {
187+
if (korean == null) {
188+
return null;
189+
}
190+
191+
return switch (korean.trim()) {
192+
case "체험·액티비티" -> TravelStyle.ACTIVITY;
193+
case "자연과 함께" -> TravelStyle.NATURE;
194+
case "여유롭게 힐링" -> TravelStyle.HEALING;
195+
case "여행지 느낌 물씬" -> TravelStyle.LOCAL_VIBE;
196+
case "관광보다 먹방" -> TravelStyle.FOOD_FOCUS;
197+
case "SNS 핫플레이스" -> TravelStyle.HOTPLACE;
198+
case "유명 관광지는 필수" -> TravelStyle.MUST_VISIT;
199+
case "문화·예술·역사" -> TravelStyle.CULTURE;
200+
case "쇼핑은 열정적으로" -> TravelStyle.SHOPPING;
201+
default -> null; // 매칭되지 않는 스타일은 무시
202+
};
203+
}
204+
205+
206+
/**
207+
* FastAPI 하이라이트 리스트를 TripHighlight 엔티티 리스트로 변환
208+
*/
209+
public static List<TripHighlight> toTripHighlightEntities(
210+
List<String> highlights,
211+
TripPlan tripPlan
212+
) {
213+
if (highlights == null) {
214+
return List.of();
215+
}
216+
return highlights.stream()
217+
.map(content -> TripHighlight.builder()
218+
.content(content)
219+
.tripPlan(tripPlan)
220+
.build())
221+
.toList();
222+
}
223+
224+
/**
225+
* FastAPI 교통편 DTO를 TripTransportation 엔티티로 변환
226+
*/
227+
public static TripTransportation toTripTransportationEntity(
228+
com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.TransportationDTO dto,
229+
TripPlan tripPlan
230+
) {
231+
if (dto == null) {
232+
return null;
233+
}
234+
return TripTransportation.builder()
235+
.origin(dto.getOrigin())
236+
.destination(dto.getDestination())
237+
.airlineName(dto.getName())
238+
.airlineName(dto.getName()) // DB 호환을 위해 name 필드에도 동일한 값 저장
239+
.price(dto.getPrice())
240+
.tripPlan(tripPlan)
241+
.build();
242+
}
243+
244+
/**
245+
* FastAPI 숙소 리스트를 TripAccommodation 엔티티 리스트로 변환
246+
*/
247+
public static List<TripAccommodation> toTripAccommodationEntities(
248+
List<com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.AccommodationDTO> accommodations,
249+
TripPlan tripPlan
250+
) {
251+
if (accommodations == null) {
252+
return List.of();
253+
}
254+
return accommodations.stream()
255+
.map(dto -> TripAccommodation.builder()
256+
.name(dto.getName())
257+
.address(dto.getAddress())
258+
.pricePerNight(dto.getPricePerNight())
259+
.tripPlan(tripPlan)
260+
.build())
261+
.toList();
262+
}
263+
264+
/**
265+
* FastAPI 일별 일정 DTO를 DailySchedule 엔티티로 변환
266+
*/
267+
public static DailySchedule toDailyScheduleEntity(
268+
com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.DailyScheduleDTO dto,
269+
TripPlan tripPlan
270+
) {
271+
DailySchedule dailySchedule = DailySchedule.builder()
272+
.day(dto.getDay())
273+
.date(dto.getDate())
274+
.tripPlan(tripPlan)
275+
.build();
276+
277+
// ScheduleItems 추가
278+
if (dto.getSchedules() != null) {
279+
for (com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.ScheduleDTO scheduleDTO : dto.getSchedules()) {
280+
ScheduleItem scheduleItem = ScheduleItem.builder()
281+
.orderIndex(scheduleDTO.getOrderIndex())
282+
.time(java.time.LocalTime.parse(scheduleDTO.getTime()))
283+
.title(scheduleDTO.getTitle())
284+
.description(scheduleDTO.getDescription())
285+
.dailySchedule(dailySchedule)
286+
.build();
287+
dailySchedule.getScheduleItems().add(scheduleItem);
288+
}
289+
}
290+
291+
return dailySchedule;
292+
}
147293
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.example.triptalk.domain.tripPlan.dto;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import lombok.Setter;
9+
10+
import java.time.LocalDate;
11+
import java.util.List;
12+
13+
public class TripPlanRequest {
14+
15+
@Getter
16+
@Setter
17+
@NoArgsConstructor
18+
@AllArgsConstructor
19+
@Schema(description = "FastAPI에서 생성된 여행 계획 생성 요청")
20+
public static class CreateFromFastAPIDTO {
21+
22+
@Schema(description = "여행 제목", example = "제주 액티비티 탐험 5박 6일 여행")
23+
private String title;
24+
25+
@Schema(description = "목적지", example = "제주도")
26+
private String destination;
27+
28+
@Schema(description = "출발지", example = "서울")
29+
private String departure;
30+
31+
@Schema(description = "여행 시작일", example = "2025-12-10")
32+
private LocalDate startDate;
33+
34+
@Schema(description = "여행 종료일", example = "2025-12-15")
35+
private LocalDate endDate;
36+
37+
@Schema(description = "동행인", example = "친구")
38+
private String companions;
39+
40+
@Schema(description = "예산", example = "70만원")
41+
private String budget;
42+
43+
@Schema(description = "여행 스타일 리스트")
44+
private List<String> travelStyles;
45+
46+
@Schema(description = "여행 하이라이트")
47+
private List<String> highlights;
48+
49+
@Schema(description = "일별 일정")
50+
private List<DailyScheduleDTO> dailySchedules;
51+
52+
@Schema(description = "출발 교통편")
53+
private TransportationDTO outboundTransportation;
54+
55+
@Schema(description = "귀환 교통편")
56+
private TransportationDTO returnTransportation;
57+
58+
@Schema(description = "숙소 리스트")
59+
private List<AccommodationDTO> accommodations;
60+
}
61+
62+
@Getter
63+
@Setter
64+
@NoArgsConstructor
65+
@AllArgsConstructor
66+
@Schema(description = "일별 일정")
67+
public static class DailyScheduleDTO {
68+
69+
@Schema(description = "일차", example = "1")
70+
private Integer day;
71+
72+
@Schema(description = "날짜", example = "2025-12-10")
73+
private LocalDate date;
74+
75+
@Schema(description = "일정 항목 리스트")
76+
private List<ScheduleDTO> schedules;
77+
}
78+
79+
@Getter
80+
@Setter
81+
@NoArgsConstructor
82+
@AllArgsConstructor
83+
@Schema(description = "일정 항목")
84+
public static class ScheduleDTO {
85+
86+
@Schema(description = "순서", example = "1")
87+
@JsonProperty("order_index")
88+
private Integer orderIndex;
89+
90+
@Schema(description = "시간", example = "07:30")
91+
private String time;
92+
93+
@Schema(description = "제목", example = "비행기 탑승")
94+
private String title;
95+
96+
@Schema(description = "설명", example = "김포 출발 제주행")
97+
private String description;
98+
}
99+
100+
@Getter
101+
@Setter
102+
@NoArgsConstructor
103+
@AllArgsConstructor
104+
@Schema(description = "교통편 정보")
105+
public static class TransportationDTO {
106+
107+
@Schema(description = "출발지", example = "김포공항")
108+
private String origin;
109+
110+
@Schema(description = "도착지", example = "제주공항")
111+
private String destination;
112+
113+
@Schema(description = "교통편명", example = "진에어LJ313")
114+
private String name;
115+
116+
@Schema(description = "가격", example = "55000")
117+
private Integer price;
118+
}
119+
120+
@Getter
121+
@Setter
122+
@NoArgsConstructor
123+
@AllArgsConstructor
124+
@Schema(description = "숙소 정보")
125+
public static class AccommodationDTO {
126+
127+
@Schema(description = "숙소명", example = "메종글래드 제주")
128+
private String name;
129+
130+
@Schema(description = "주소", example = "제주시 노연로 80")
131+
private String address;
132+
133+
@Schema(description = "1박 가격", example = "100000")
134+
private Integer pricePerNight;
135+
}
136+
}
137+

src/main/java/com/example/triptalk/domain/tripPlan/entity/ScheduleItem.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ public class ScheduleItem extends BaseEntity {
2121
@Column(nullable = false)
2222
private LocalTime time;
2323

24-
@Column(length = 10, nullable = false)
24+
@Column(length = 50, nullable = false)
2525
private String title;
2626

27-
@Column(length = 20, nullable = false)
27+
@Column(length = 100, nullable = false)
2828
private String description;
2929

3030
@ManyToOne(fetch = FetchType.LAZY)

src/main/java/com/example/triptalk/domain/tripPlan/entity/TripAccommodation.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
@Entity
1313
public class TripAccommodation extends BaseEntity {
1414

15-
@Column(length = 20, nullable = false)
15+
@Column(length = 100, nullable = false)
1616
private String name;
1717

18-
@Column(length = 20, nullable = false)
18+
@Column(length = 100, nullable = false)
1919
private String address;
2020

2121
@Column(nullable = false)

src/main/java/com/example/triptalk/domain/tripPlan/entity/TripHighlight.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
@Entity
1313
public class TripHighlight extends BaseEntity {
1414

15-
@Column(length = 15, nullable = false)
15+
@Column(length = 100, nullable = false)
1616
private String content;
1717

1818
@ManyToOne(fetch = FetchType.LAZY)

src/main/java/com/example/triptalk/domain/tripPlan/entity/TripPlan.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
@Entity
1919
public class TripPlan extends BaseEntity {
2020

21-
@Column(length = 20, nullable = false)
21+
@Column(length = 100, nullable = false)
2222
private String title;
2323

24-
@Column(length = 10, nullable = false)
24+
@Column(length = 50, nullable = false)
2525
private String destination;
2626

27-
@Column(length = 10, nullable = false)
27+
@Column(length = 50, nullable = false)
2828
private String departure;
2929

3030
@Column(nullable = false)
@@ -33,10 +33,10 @@ public class TripPlan extends BaseEntity {
3333
@Column(nullable = false)
3434
private LocalDate endDate;
3535

36-
@Column(length = 30, nullable = false)
36+
@Column(length = 50, nullable = false)
3737
private String companions;
3838

39-
@Column(length = 10, nullable = false)
39+
@Column(length = 30, nullable = false)
4040
private String budget;
4141

4242
@ElementCollection(fetch = FetchType.LAZY)

0 commit comments

Comments
 (0)