-
Notifications
You must be signed in to change notification settings - Fork 1
[#22] Feat: 여행 일정 저장 API 구현 #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughFastAPI 통합 기능을 추가하여 외부 시스템에서 여행 계획을 생성할 수 있도록 한다. 새로운 API 엔드포인트, DTO, 변환 유틸리티, 서비스 로직을 추가하고 관련 엔티티의 컬럼 길이 제약을 확장한다. Changes
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (6)
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripTransportation.java (1)
21-22: 필드명airlineName이 항공편에만 한정됩니다.
name에서airlineName으로 변경되어 항공편만 지원하는 것처럼 보입니다. 향후 기차, 버스 등 다른 교통수단을 지원할 계획이 있다면carrierName또는transportName과 같은 범용적인 이름을 고려하세요.src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java (3)
28-28: 완전한 클래스명 대신 import 문을 사용하세요.
UserRepository와 다른 클래스들에 대해 완전한 클래스명을 사용하고 있습니다. 상단에 import 문을 추가하여 코드 가독성을 개선하세요.+import com.example.triptalk.domain.user.repository.UserRepository; +import com.example.triptalk.domain.user.entity.User; +import com.example.triptalk.domain.tripPlan.dto.TripPlanRequest; +import java.util.List;- private final com.example.triptalk.domain.user.repository.UserRepository userRepository; + private final UserRepository userRepository;
142-151: DailySchedule 저장 시 배치 처리를 사용하세요.루프 내에서 개별
save()호출은 N+1 쿼리 문제를 유발합니다. 리스트로 수집 후saveAll()을 사용하면 성능이 개선됩니다.// 6. DailySchedules 저장 (Converter 사용) if (request.getDailySchedules() != null) { - for (com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.DailyScheduleDTO dailyScheduleDTO : request.getDailySchedules()) { - DailySchedule dailySchedule = TripPlanConverter.toDailyScheduleEntity( - dailyScheduleDTO, - finalTripPlan - ); - dailyScheduleRepository.save(dailySchedule); - } + List<DailySchedule> dailySchedules = request.getDailySchedules().stream() + .map(dto -> TripPlanConverter.toDailyScheduleEntity(dto, finalTripPlan)) + .toList(); + dailyScheduleRepository.saveAll(dailySchedules); }
109-109: 불필요한 변수finalTripPlan제거 가능합니다.
tripPlan이 재할당된 후 다시 할당되지 않으므로,finalTripPlan변수 없이tripPlan을 직접 사용해도 됩니다.src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java (2)
177-177: 하드코딩된 기본 이미지 URL을 상수로 추출하는 것을 고려해보세요.기본 이미지 URL이 하드코딩되어 있습니다. 향후 변경이나 재사용을 위해 클래스 상단에 상수로 추출하는 것이 좋습니다.
+ private static final String DEFAULT_TRIP_IMAGE_URL = + "https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=800"; + // ... 기존 코드 ... public static TripPlan toTripPlanEntity( com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO request, com.example.triptalk.domain.user.entity.User user ) { // ... 기존 코드 ... return TripPlan.builder() // ... 기존 필드 ... - .imgUrl("https://images.unsplash.com/photo-1488646953014-85cb44e25828?w=800") + .imgUrl(DEFAULT_TRIP_IMAGE_URL) .status(com.example.triptalk.domain.tripPlan.enums.TripStatus.PLANNED) .user(user) .build(); }
186-203: 매칭되지 않는 여행 스타일에 대한 로깅 추가를 고려해보세요.현재 매칭되지 않는 여행 스타일은 조용히 무시됩니다. 디버깅과 모니터링을 위해 경고 로그를 추가하는 것이 좋습니다.
+ private static final org.slf4j.Logger log = + org.slf4j.LoggerFactory.getLogger(TripPlanConverter.class); + private static TravelStyle mapKoreanToTravelStyle(String korean) { if (korean == null) { return null; } - return switch (korean.trim()) { + TravelStyle style = switch (korean.trim()) { case "체험·액티비티" -> TravelStyle.ACTIVITY; case "자연과 함께" -> TravelStyle.NATURE; case "여유롭게 힐링" -> TravelStyle.HEALING; case "여행지 느낌 물씬" -> TravelStyle.LOCAL_VIBE; case "관광보다 먹방" -> TravelStyle.FOOD_FOCUS; case "SNS 핫플레이스" -> TravelStyle.HOTPLACE; case "유명 관광지는 필수" -> TravelStyle.MUST_VISIT; case "문화·예술·역사" -> TravelStyle.CULTURE; case "쇼핑은 열정적으로" -> TravelStyle.SHOPPING; - default -> null; // 매칭되지 않는 스타일은 무시 + default -> null; }; + + if (style == null) { + log.warn("Unknown travel style from FastAPI: {}", korean); + } + + return style; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
src/main/java/com/example/triptalk/domain/tripPlan/controller/TripPlanController.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java(2 hunks)src/main/java/com/example/triptalk/domain/tripPlan/dto/TripPlanRequest.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/ScheduleItem.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/TripAccommodation.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/TripHighlight.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/TripPlan.java(2 hunks)src/main/java/com/example/triptalk/domain/tripPlan/entity/TripTransportation.java(1 hunks)src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanService.java(2 hunks)src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java (1)
TripPlanConverter(12-293)
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanService.java (1)
src/main/java/com/example/triptalk/domain/tripPlan/dto/TripPlanRequest.java (1)
TripPlanRequest(13-136)
🔇 Additional comments (9)
src/main/java/com/example/triptalk/domain/tripPlan/entity/ScheduleItem.java (1)
24-28: LGTM! 컬럼 길이 확장이 적절합니다.title(50)과 description(100)의 길이 확장이 FastAPI 통합 데이터를 수용하기에 적합합니다. 기존 데이터베이스에 스키마 마이그레이션이 필요한지 확인하세요.
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripHighlight.java (1)
15-16: LGTM! 하이라이트 콘텐츠 길이 확장이 적절합니다.100자 길이는 여행 하이라이트를 충분히 표현할 수 있습니다.
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanService.java (1)
17-19: LGTM! 인터페이스 메서드 선언이 적절합니다.기존 메서드 패턴과 일관되며, 메서드명이 FastAPI 통합 목적을 명확히 표현합니다.
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripAccommodation.java (1)
15-19: LGTM! 숙소 정보 컬럼 길이 확장이 적절합니다.name과 address 필드의 100자 확장이 다양한 숙소 정보를 수용하기에 충분합니다.
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripPlan.java (1)
21-40: LGTM! TripPlan 엔티티의 컬럼 길이 확장이 적절합니다.각 필드의 길이가 FastAPI에서 생성된 여행 계획 데이터를 수용하기에 충분하도록 확장되었습니다.
src/main/java/com/example/triptalk/domain/tripPlan/dto/TripPlanRequest.java (1)
86-88: JSON 필드명 매핑이 적절합니다.
@JsonProperty("order_index")를 사용하여 Java의 camelCase 필드명과 JSON의 snake_case 필드명을 올바르게 매핑했습니다.src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java (3)
54-54: 필드명 변경이 적절합니다.
getName()에서getAirlineName()으로 변경하여 엔티티 필드명과 일관성을 유지했습니다.
209-222: LGTM!null 처리가 적절하며, 변환 로직이 명확합니다.
247-262: LGTM!null 처리가 적절하며, 변환 로직이 명확합니다.
| public ApiResponse<TripPlanResponse.TripPlanDTO> createTripPlanFromFastAPI( | ||
| @RequestBody com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO request, | ||
| HttpServletRequest httpRequest | ||
| ) { | ||
| Long userId = authUtil.getUserIdFromRequest(httpRequest); | ||
| TripPlanResponse.TripPlanDTO response = tripPlanService.createTripPlanFromFastAPI(userId, request); | ||
| return ApiResponse.onSuccess(response); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
@Valid 어노테이션 누락 및 import 개선이 필요합니다.
요청 본문에 @Valid 어노테이션을 추가하여 DTO 필드 검증을 활성화하세요. 또한 완전한 클래스명 대신 import 문을 사용하면 가독성이 향상됩니다.
+import com.example.triptalk.domain.tripPlan.dto.TripPlanRequest;
+import jakarta.validation.Valid; public ApiResponse<TripPlanResponse.TripPlanDTO> createTripPlanFromFastAPI(
- @RequestBody com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO request,
+ @Valid @RequestBody TripPlanRequest.CreateFromFastAPIDTO request,
HttpServletRequest httpRequest
) {Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/controller/TripPlanController.java
around lines 80 to 86, the controller method is missing the @Valid annotation on
the request body and uses a fully-qualified DTO type; add an import for the DTO
(com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.CreateFromFastAPIDTO)
and add an import for javax.validation.Valid (or jakarta.validation.Valid if
your project uses Jakarta), then change the method parameter to use the imported
type and annotate it with @Valid before @RequestBody (e.g. public
ApiResponse<...> createTripPlanFromFastAPI(@Valid @RequestBody
CreateFromFastAPIDTO request, HttpServletRequest httpRequest) { ... }) so bean
validation runs and the code is more readable.
| return TripTransportation.builder() | ||
| .origin(dto.getOrigin()) | ||
| .destination(dto.getDestination()) | ||
| .airlineName(dto.getName()) | ||
| .airlineName(dto.getName()) // DB 호환을 위해 name 필드에도 동일한 값 저장 | ||
| .price(dto.getPrice()) | ||
| .tripPlan(tripPlan) | ||
| .build(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "TripTransportation.java" -type fRepository: TripTalk/BE_SpringBoot
Length of output: 149
🏁 Script executed:
cat -n ./src/main/java/com/example/triptalk/domain/tripPlan/entity/TripTransportation.javaRepository: TripTalk/BE_SpringBoot
Length of output: 1007
🏁 Script executed:
sed -n '227,242p' ./src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java | cat -nRepository: TripTalk/BE_SpringBoot
Length of output: 798
중복된 .airlineName() 호출을 제거하십시오.
lines 237-238에서 .airlineName(dto.getName())이 중복으로 호출되고 있습니다. TripTransportation 엔티티에는 airlineName 필드만 존재하고 name 필드가 없으므로, 중복된 호출은 불필요합니다. 한 줄을 삭제하여 다음과 같이 수정하세요:
return TripTransportation.builder()
.origin(dto.getOrigin())
.destination(dto.getDestination())
.airlineName(dto.getName())
- .airlineName(dto.getName()) // DB 호환을 위해 name 필드에도 동일한 값 저장
.price(dto.getPrice())
.tripPlan(tripPlan)
.build();🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java
around lines 234 to 241, remove the duplicated .airlineName(dto.getName()) call
(lines 237-238) so the builder only sets airlineName once; keep the single
.airlineName(dto.getName()) and delete the redundant line to avoid unnecessary
duplicate assignment for the TripTransportation entity.
| // ScheduleItems 추가 | ||
| if (dto.getSchedules() != null) { | ||
| for (com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.ScheduleDTO scheduleDTO : dto.getSchedules()) { | ||
| ScheduleItem scheduleItem = ScheduleItem.builder() | ||
| .orderIndex(scheduleDTO.getOrderIndex()) | ||
| .time(java.time.LocalTime.parse(scheduleDTO.getTime())) | ||
| .title(scheduleDTO.getTitle()) | ||
| .description(scheduleDTO.getDescription()) | ||
| .dailySchedule(dailySchedule) | ||
| .build(); | ||
| dailySchedule.getScheduleItems().add(scheduleItem); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LocalTime.parse()에 대한 예외 처리가 필요합니다.
Line 282에서 LocalTime.parse(scheduleDTO.getTime())을 호출할 때 예외 처리가 없습니다. time 값이 null이거나 잘못된 형식인 경우 DateTimeParseException이 발생하여 전체 여행 계획 저장이 실패할 수 있습니다.
다음 중 하나의 방법으로 수정을 권장합니다:
방법 1: 예외 처리 추가 (권장)
// ScheduleItems 추가
if (dto.getSchedules() != null) {
for (com.example.triptalk.domain.tripPlan.dto.TripPlanRequest.ScheduleDTO scheduleDTO : dto.getSchedules()) {
+ if (scheduleDTO.getTime() == null) {
+ throw new IllegalArgumentException("일정 시간은 필수입니다");
+ }
+
+ java.time.LocalTime parsedTime;
+ try {
+ parsedTime = java.time.LocalTime.parse(scheduleDTO.getTime());
+ } catch (java.time.format.DateTimeParseException e) {
+ throw new IllegalArgumentException(
+ "잘못된 시간 형식입니다: " + scheduleDTO.getTime() + " (예: 07:30)", e);
+ }
+
ScheduleItem scheduleItem = ScheduleItem.builder()
.orderIndex(scheduleDTO.getOrderIndex())
- .time(java.time.LocalTime.parse(scheduleDTO.getTime()))
+ .time(parsedTime)
.title(scheduleDTO.getTitle())
.description(scheduleDTO.getDescription())
.dailySchedule(dailySchedule)
.build();
dailySchedule.getScheduleItems().add(scheduleItem);
}
}방법 2: DTO 레벨에서 검증
ScheduleDTO에 @NotBlank와 @Pattern 어노테이션 추가:
@Schema(description = "시간", example = "07:30")
@NotBlank(message = "일정 시간은 필수입니다")
@Pattern(regexp = "^([01]\\d|2[0-3]):[0-5]\\d$", message = "시간 형식은 HH:mm 이어야 합니다")
private String time;🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java
around lines 277 to 289, the call to LocalTime.parse(scheduleDTO.getTime()) can
throw DateTimeParseException or NPE for null/blank input; update the loop to
first validate scheduleDTO.getTime() is not null/blank and then perform parsing
inside a try-catch that catches DateTimeParseException (and optionally
NullPointerException), and on failure throw a clear validation exception (e.g.,
IllegalArgumentException or your app's BadRequestException) with a message
including the offending schedule's orderIndex and raw value so callers get a
meaningful error instead of an obscure parse crash; ensure the rest of the
ScheduleItem construction uses the parsed LocalTime variable.
| @Getter | ||
| @Setter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| @Schema(description = "FastAPI에서 생성된 여행 계획 생성 요청") | ||
| public static class CreateFromFastAPIDTO { | ||
|
|
||
| @Schema(description = "여행 제목", example = "제주 액티비티 탐험 5박 6일 여행") | ||
| private String title; | ||
|
|
||
| @Schema(description = "목적지", example = "제주도") | ||
| private String destination; | ||
|
|
||
| @Schema(description = "출발지", example = "서울") | ||
| private String departure; | ||
|
|
||
| @Schema(description = "여행 시작일", example = "2025-12-10") | ||
| private LocalDate startDate; | ||
|
|
||
| @Schema(description = "여행 종료일", example = "2025-12-15") | ||
| private LocalDate endDate; | ||
|
|
||
| @Schema(description = "동행인", example = "친구") | ||
| private String companions; | ||
|
|
||
| @Schema(description = "예산", example = "70만원") | ||
| private String budget; | ||
|
|
||
| @Schema(description = "여행 스타일 리스트") | ||
| private List<String> travelStyles; | ||
|
|
||
| @Schema(description = "여행 하이라이트") | ||
| private List<String> highlights; | ||
|
|
||
| @Schema(description = "일별 일정") | ||
| private List<DailyScheduleDTO> dailySchedules; | ||
|
|
||
| @Schema(description = "출발 교통편") | ||
| private TransportationDTO outboundTransportation; | ||
|
|
||
| @Schema(description = "귀환 교통편") | ||
| private TransportationDTO returnTransportation; | ||
|
|
||
| @Schema(description = "숙소 리스트") | ||
| private List<AccommodationDTO> accommodations; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필수 필드에 대한 유효성 검증 추가를 권장합니다.
FastAPI에서 받은 데이터를 저장하기 전에 데이터 무결성을 보장하기 위해 필수 필드들에 대한 유효성 검증 어노테이션을 추가하는 것이 좋습니다. 예를 들어:
title,destination,departure:@NotBlankstartDate,endDate:@NotNulldailySchedules:@NotEmpty또는@Valid
이를 통해 잘못된 데이터가 저장되는 것을 사전에 방지할 수 있습니다.
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+
public class TripPlanRequest {
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "FastAPI에서 생성된 여행 계획 생성 요청")
public static class CreateFromFastAPIDTO {
@Schema(description = "여행 제목", example = "제주 액티비티 탐험 5박 6일 여행")
+ @NotBlank(message = "여행 제목은 필수입니다")
private String title;
@Schema(description = "목적지", example = "제주도")
+ @NotBlank(message = "목적지는 필수입니다")
private String destination;
@Schema(description = "출발지", example = "서울")
+ @NotBlank(message = "출발지는 필수입니다")
private String departure;
@Schema(description = "여행 시작일", example = "2025-12-10")
+ @NotNull(message = "여행 시작일은 필수입니다")
private LocalDate startDate;
@Schema(description = "여행 종료일", example = "2025-12-15")
+ @NotNull(message = "여행 종료일은 필수입니다")
private LocalDate endDate;
// ... 나머지 필드
@Schema(description = "일별 일정")
+ @NotEmpty(message = "일별 일정은 최소 1개 이상이어야 합니다")
+ @Valid
private List<DailyScheduleDTO> dailySchedules;Committable suggestion skipped: line range outside the PR's diff.
| // 3. Highlights 저장 (Converter 사용) | ||
| java.util.List<TripHighlight> highlights = TripPlanConverter.toTripHighlightEntities( | ||
| request.getHighlights(), | ||
| finalTripPlan | ||
| ); | ||
| tripHighlightRepository.saveAll(highlights); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
highlights가 null인 경우 NPE가 발생할 수 있습니다.
request.getHighlights()가 null을 반환하면 toTripHighlightEntities 내부 또는 saveAll 호출 시 NPE가 발생할 수 있습니다. 방어적 null 체크를 추가하세요.
// 3. Highlights 저장 (Converter 사용)
- java.util.List<TripHighlight> highlights = TripPlanConverter.toTripHighlightEntities(
- request.getHighlights(),
- finalTripPlan
- );
- tripHighlightRepository.saveAll(highlights);
+ if (request.getHighlights() != null && !request.getHighlights().isEmpty()) {
+ List<TripHighlight> highlights = TripPlanConverter.toTripHighlightEntities(
+ request.getHighlights(),
+ finalTripPlan
+ );
+ tripHighlightRepository.saveAll(highlights);
+ }🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java
around lines 111-116, the call to
TripPlanConverter.toTripHighlightEntities(request.getHighlights(),
finalTripPlan) and subsequent tripHighlightRepository.saveAll(highlights) can
throw an NPE when request.getHighlights() is null; add a defensive null/empty
check before conversion (e.g., if request.getHighlights() == null ||
request.getHighlights().isEmpty() skip conversion and save), or coerce to an
empty list (Collections.emptyList()) before passing to the converter so saveAll
is only called with a non-null collection.
| // 5. Accommodations 저장 (Converter 사용) | ||
| java.util.List<TripAccommodation> accommodations = TripPlanConverter.toTripAccommodationEntities( | ||
| request.getAccommodations(), | ||
| finalTripPlan | ||
| ); | ||
| tripAccommodationRepository.saveAll(accommodations); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
accommodations가 null인 경우 NPE가 발생할 수 있습니다.
request.getAccommodations()가 null을 반환하면 예외가 발생합니다. dailySchedules처럼 null 체크를 추가하세요.
// 5. Accommodations 저장 (Converter 사용)
- java.util.List<TripAccommodation> accommodations = TripPlanConverter.toTripAccommodationEntities(
- request.getAccommodations(),
- finalTripPlan
- );
- tripAccommodationRepository.saveAll(accommodations);
+ if (request.getAccommodations() != null && !request.getAccommodations().isEmpty()) {
+ List<TripAccommodation> accommodations = TripPlanConverter.toTripAccommodationEntities(
+ request.getAccommodations(),
+ finalTripPlan
+ );
+ tripAccommodationRepository.saveAll(accommodations);
+ }🤖 Prompt for AI Agents
In
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java
around lines 135-140, the code calls
TripPlanConverter.toTripAccommodationEntities(request.getAccommodations(),
finalTripPlan) without checking for null which can cause an NPE if
request.getAccommodations() is null; add a null-check similar to dailySchedules:
only call the converter when request.getAccommodations() != null (or treat null
as empty), produce a List<TripAccommodation> (empty if null), and call
tripAccommodationRepository.saveAll(accommodations) only when the resulting list
is not empty (or let saveAll receive Collections.emptyList() if preferred), so
no NPE occurs and no unnecessary DB calls are made.
#️⃣ 연관된 이슈
📝 작업 내용
📌 공유 사항
✅ 체크리스트
스크린샷 (선택)
💬 리뷰 요구사항 (선택)
Summary by CodeRabbit
릴리스 노트
✏️ Tip: You can customize this high-level summary in your review settings.