Skip to content

Conversation

@Yujin1219
Copy link
Member

@Yujin1219 Yujin1219 commented Dec 11, 2025

#️⃣ 연관된 이슈

📝 작업 내용

  • 여행 일정 저장 API 구현

📌 공유 사항

✅ 체크리스트

  • Reviewer에 팀원들을 선택 했나요?
  • Assignees에 본인을 선택 했나요?
  • Merge 하려는 브랜치가 올바르게 설정되어 있나요?
  • 컨벤션을 지키고 있나요?
  • 로컬에서 실행했을 때 에러가 발생하지 않나요?
  • 불필요한 주석이 제거되었나요?
  • 코드 스타일이 일관적인가요?

스크린샷 (선택)

💬 리뷰 요구사항 (선택)

ex) 메서드 XXX의 이름을 더 잘 짓고 싶은데 혹시 좋은 명칭이 있을까요? or 변경 사항 등

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 외부 시스템으로부터 여행 계획을 생성할 수 있는 새로운 기능이 추가되었습니다.
    • 여행 일정, 숙박, 교통, 여행 하이라이트 등 상세한 여행 정보를 포함한 완전한 여행 계획을 더욱 효율적으로 생성할 수 있습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

@Yujin1219 Yujin1219 self-assigned this Dec 11, 2025
@Yujin1219 Yujin1219 added the ✨ feature 새로운 기능 개발 label Dec 11, 2025
@Yujin1219 Yujin1219 linked an issue Dec 11, 2025 that may be closed by this pull request
1 task
@Yujin1219 Yujin1219 merged commit 900e272 into develop Dec 11, 2025
2 checks passed
@Yujin1219 Yujin1219 deleted the feat/#22 branch December 11, 2025 03:52
@coderabbitai
Copy link

coderabbitai bot commented Dec 11, 2025

Walkthrough

FastAPI 통합 기능을 추가하여 외부 시스템에서 여행 계획을 생성할 수 있도록 한다. 새로운 API 엔드포인트, DTO, 변환 유틸리티, 서비스 로직을 추가하고 관련 엔티티의 컬럼 길이 제약을 확장한다.

Changes

Cohort / File(s) Summary
API 엔드포인트
src/main/java/com/example/triptalk/domain/tripPlan/controller/TripPlanController.java
POST /api/trip-plan/from-fastapi 엔드포인트 추가. CreateFromFastAPIDTO를 요청 본문으로 받아 userId를 추출한 후 서비스에 위임하고 TripPlanDTO를 반환.
요청 DTO
src/main/java/com/example/triptalk/domain/tripPlan/dto/TripPlanRequest.java
새로운 TripPlanRequest 컨테이너 클래스 추가. CreateFromFastAPIDTO, DailyScheduleDTO, ScheduleDTO, TransportationDTO, AccommodationDTO 등 내부 클래스 정의. 각 클래스는 Lombok 및 Swagger 애너테이션으로 구성.
데이터 변환 유틸리티
src/main/java/com/example/triptalk/domain/tripPlan/converter/TripPlanConverter.java
toTripPlanEntity, toTripHighlightEntities, toTripTransportationEntity, toTripAccommodationEntities, toDailyScheduleEntity 등 FastAPI DTO 변환 메서드 추가. mapKoreanToTravelStyle 헬퍼 메서드 추가. 기존 toTransportationDTO의 항공사명 매핑 변경 (t.getName() → t.getAirlineName()).
서비스 계층
src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanService.java, src/main/java/com/example/triptalk/domain/tripPlan/service/TripPlanServiceImpl.java
TripPlanService에 createTripPlanFromFastAPI 메서드 서명 추가. TripPlanServiceImpl에 메서드 구현 추가. UserRepository 의존성 주입. 사용자 조회, 엔티티 변환, 관련 데이터(강조 항목, 교통, 숙박, 일정) 저장 처리.
엔티티 컬럼 길이 확장
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripPlan.java
title (20 → 100), destination (10 → 50), departure (10 → 50), companions (30 → 50), budget (10 → 30) 컬럼 길이 증가.
교통 엔티티 수정
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripTransportation.java
name 필드 제거, airlineName 필드 추가 (airline_name 컬럼 매핑). origin, destination 컬럼 길이 10 → 50 증가.
숙박/강조/일정 엔티티 수정
src/main/java/com/example/triptalk/domain/tripPlan/entity/TripAccommodation.java, src/main/java/com/example/triptalk/domain/tripPlan/entity/TripHighlight.java, src/main/java/com/example/triptalk/domain/tripPlan/entity/ScheduleItem.java
TripAccommodation: name, address 길이 20 → 100. TripHighlight: content 길이 15 → 100. ScheduleItem: title 길이 10 → 50, description 길이 20 → 100.

Suggested reviewers

  • ys1217-gg
  • davidcho0701

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 핵심 변경 사항인 '여행 일정 저장 API 구현'을 명확하게 요약하고 있어 주요 변경 내용과 완전히 일치합니다.
Description check ✅ Passed PR 설명은 필수 섹션들을 포함하고 있으나, '공유 사항' 섹션이 비어있고 일부 체크리스트 항목이 미완료되어 있습니다. 하지만 관련 이슈 번호와 작업 내용이 작성되어 있어 주요 정보는 충분합니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#22

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 80ab4eb and 28b7646.

📒 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 처리가 적절하며, 변환 로직이 명확합니다.

Comment on lines +80 to +86
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);
Copy link

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.

Comment on lines +234 to +241
return TripTransportation.builder()
.origin(dto.getOrigin())
.destination(dto.getDestination())
.airlineName(dto.getName())
.airlineName(dto.getName()) // DB 호환을 위해 name 필드에도 동일한 값 저장
.price(dto.getPrice())
.tripPlan(tripPlan)
.build();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "TripTransportation.java" -type f

Repository: TripTalk/BE_SpringBoot

Length of output: 149


🏁 Script executed:

cat -n ./src/main/java/com/example/triptalk/domain/tripPlan/entity/TripTransportation.java

Repository: 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 -n

Repository: 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.

Comment on lines +277 to +289
// 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);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +15 to +60
@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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

필수 필드에 대한 유효성 검증 추가를 권장합니다.

FastAPI에서 받은 데이터를 저장하기 전에 데이터 무결성을 보장하기 위해 필수 필드들에 대한 유효성 검증 어노테이션을 추가하는 것이 좋습니다. 예를 들어:

  • title, destination, departure: @NotBlank
  • startDate, endDate: @NotNull
  • dailySchedules: @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.

Comment on lines +111 to +116
// 3. Highlights 저장 (Converter 사용)
java.util.List<TripHighlight> highlights = TripPlanConverter.toTripHighlightEntities(
request.getHighlights(),
finalTripPlan
);
tripHighlightRepository.saveAll(highlights);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +135 to +140
// 5. Accommodations 저장 (Converter 사용)
java.util.List<TripAccommodation> accommodations = TripPlanConverter.toTripAccommodationEntities(
request.getAccommodations(),
finalTripPlan
);
tripAccommodationRepository.saveAll(accommodations);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ feature 새로운 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feat: 여행 일정 저장 API 구현

2 participants