Skip to content

[FEAT] 푸드트럭 정보 등록/수정 api 구현#51

Merged
buzz0331 merged 15 commits into
developfrom
feat/#49-foodtruck-put-info
Oct 28, 2025
Merged

[FEAT] 푸드트럭 정보 등록/수정 api 구현#51
buzz0331 merged 15 commits into
developfrom
feat/#49-foodtruck-put-info

Conversation

@buzz0331

@buzz0331 buzz0331 commented Oct 21, 2025

Copy link
Copy Markdown
Contributor

#️⃣연관된 이슈

📝작업 내용

크게 2가지 api를 개발하였습니다.

푸드트럭 정보 등록/수정 API

  1. 푸드트럭 소유주가 맞는지, 승인 상태가 맞는지 검증
  2. 푸드트럭 정보 업데이트
  3. 푸드트럭 노출 상태 변경 허용 (위 API가 최소 1번을 호출되어야 노출 상태 변경이 가능해야한다는 요구사항을 충족시키기 위함)
  4. 서비스 가능 지역 동기화 (추가된 지역이 있다면 -> insert / 삭제된 지역이 있다면 -> delete가 호출되도록 함)
  5. 운영 가능 날짜 동기화 (기존에 존재하는 모든 AvailabeDate를 제거 후, 새로 모든 AvailabeDate를 insert)

S3에서 푸드트럭 이미지 객체 삭제 API

상균님이 이미 구현해두신 푸드트럭 메뉴 삭제 API에서 이미 메뉴 이미지를 S3에서 삭제하고 있어서 메뉴는 포함하지 않도록 명세했습니다.

  1. 푸드트럭 소유주 검증
  2. 요청 받은 이미지 url 리스트에서 key를 추출하여 각각 S3에서 삭제

기타 리팩토링 사항

  • FoodTruck 객체가 너무 방대해지는 것 같고 FoodTruckCommandService를 FoodTruckInfoService로 네이밍을 수정함에 따라 FoodTruckInfo 객체를 추출하여 VO로 따로 선언하였습니다.
  • PM님의 요구사항에 따라 기존에 전기 사용 여부의 단어를 가능/불가능 -> 필요/불필요로 변경하였습니다. (혹시 누락된 API 있으면 확인 부탁드릴게요~)
  • FoodTruck 테이블에 canChangeViewStatus boolean 플래그 추가 (푸드트럭 정보기입이 되어야 푸드트럭 노출 상태가 변경될 수 있게 했으면 좋겠다는 요구사항 반영)
  • 기존에 상균님이 구현해둔 EnumValueConverter는 @RequestParam이나 @ParameterObject에서만 자동 파싱이되고 RequestBody의 경우 JSON 형식이기 때문에 jackson에서 파싱을 담당하여 ENUM 자동 파싱이 이루어지지 않아, 각 Enum의 value를 매핑하는 메서드에 @JsonCreator를 추가하여 jackson에서 파싱시에 value 값을 자동으로 파싱하도록 하였습니다. (확인해봤을 때, 상균님이 지금까지 requestBody에서 사용하신 Enum은 대부분 status에 관련된 값이라 FoodTruckInfo에 존재하는 Enum들에만 우선 어노테이션 적용하였습니다.)
스크린샷 2025-10-22 오전 3 07 51

스크린샷

스크린샷 2025-10-22 오전 3 15 52

푸드트럭 정보 등록/수정 시 API 호출 흐름
스크린샷 2025-10-22 오전 3 30 44

💬리뷰 요구사항

위에서 말씀드린 것처럼 현재 구현되어 있는 푸드트럭 메뉴 삭제 API에서 S3에 삭제까지 담당하고 있는데, 그대로 유지할지 혹은 이번에 구현한 API에 메뉴 이미지 url도 함께 포함하여 보내도록 할지 고민이 되었는데 이 부분에 대한 상균님 의견 궁금합니다~

Summary by CodeRabbit

  • 새로운 기능

    • 내 푸드트럭 정보 수정 API 추가(수정 후 푸드트럭 ID 반환)
    • 푸드트럭 이미지 일괄 삭제 API 추가
  • 개선 사항

    • 메뉴 가격 응답 타입을 문자열에서 정수로 변경
    • 전기 사용 여부 표현값 변경: "필요/불필요/논의 필요"
    • 가능 수량에 "200인분 미만" 옵션 추가
    • 입력 검증 강화(사진, 서비스지역, 가능일 등) 및 오류 코드/설명 보강
    • 예약 상세에 리뷰 작성 필요 여부 표시 추가

@coderabbitai

coderabbitai Bot commented Oct 21, 2025

Copy link
Copy Markdown

Walkthrough

푸드트럭 관련 조회/검증 로직을 FoodTruckInfoService로 이동하고 FoodTruckCommandService를 제거했으며, 푸드트럭 정보 업데이트와 S3 이미지 삭제 엔드포인트·서비스·DTO를 추가하고 도메인 모델을 FoodTruckInfo 임베디드로 재구성했습니다.

Changes

코호트 / 파일(s) 변경 요약
서비스 리팩토링 & 엔드포인트 추가
src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java, src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java, src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java, src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java
FoodTruckCommandService 삭제. 조회/이름중복 검증을 FoodTruckInfoService로 이전. updateMyFoodTruckInfodeleteFoodTruckImagesFromS3 메서드/엔드포인트 추가(소유자 검증 포함, 이미지 삭제는 CDN URL → S3 키 추출 후 S3Service.delete 호출).
임베디드 값 객체 도입
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java, src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java
푸드트럭 주요 속성들을 FoodTruckInfo로 이동. 뷰 상태 변경 제어(canChangeViewedStatus, permitChangeViewStatus, validateApprovedStatus) 및 updateFoodTruckInfo 추가.
연관 도메인 모델/팩토리 변경
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java, src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java
Lombok @Builder, @AllArgsConstructor 추가 및 정적 팩토리 메서드(create...) 추가.
값 객체·열거형 JSON 역직렬화 및 검증 개선
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/*.java
MenuCategory, AvailableQuantity, NeedElectricity, PaymentMethod 등에 @JsonCreator 기반 from(String) 추가 및 불일치 시 DomainRuleException 사용. NeedElectricity 표시값 변경, AvailableQuantity 새 상수 추가.
리포지토리/쿼리 경로 조정
src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java, src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java, src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java
existsByNameexistsByFoodTruckInfo_Name로 경로 변경. FoodTruckServiceAreaRepositoryfindAllByFoodTruckId, findByFoodTruckIdAndRegionId 추가. QueryDSL 구현에서 foodTruck.foodTruckInfo 별칭 사용으로 경로 수정.
프레젠테이션 — 컨트롤러/요청 DTO 추가·수정
src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java, .../UpdateFoodTruckInfoRequest.java, .../DeleteFoodTruckImagesRequest.java, .../FoodTruckSearchRequest.java
PUT /{foodTruckId} (updateMyFoodTruckInfo) 및 DELETE /{foodTruckId}/images 추가. UpdateFoodTruckInfoRequest, DeleteFoodTruckImagesRequest 신규 DTO 추가. 검색 DTO의 needElectricity/availableQuantity 허용값 업데이트.
프레젠테이션 — 응답 DTO 변경
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java, .../FoodTruckIdResponse.java, src/main/java/konkuk/chacall/domain/*/presentation/dto/response/*
FoodTruckIdResponse 추가. 다수 응답 DTO가 foodTruck.getFoodTruckInfo() 경유로 이미지·이름·설명 접근 방식 변경.
메뉴 가격 타입 변경
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java, src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java, src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckMenuResponse.java
Menu.parsingMenuPrice() 삭제. 응답 DTO의 price 타입을 Stringint로 변경하고 menu.getPrice() 사용하도록 수정.
에러코드·Swagger·설정 변경
src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java, .../swagger/SwaggerResponseDescription.java, src/main/resources/application-local.yml
새로운 ErrorCode들(예: FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED 등) 추가. Swagger 응답 설명에 UPDATE_FOOD_TRUCK_INFO, DELETE_FOOD_TRUCK_IMAGES 항목 추가. 로컬 설정의 spring.jpa.hibernate.ddl-autocreateupdate로 변경.
회원·평점 연동
src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java, src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java
RatingRepository.existsByMemberAndReservation 추가 및 MemberReservationService에서 리뷰 필요 여부(reviewRequired) 조회 및 응답 반영.
사소한 흐름 변경
src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java
메뉴 삭제 시 S3 이미지 삭제 호출을 주석 처리하여 이미지 정리 로직 비활성화(주석 처리된 key 추출·s3 delete 호출 유지).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Ctrl as FoodTruckController
    participant Svc as FoodTruckService
    participant Info as FoodTruckInfoService
    participant Img as FoodTruckImageService
    participant Repo as FoodTruckRepository
    participant S3 as S3Service

    rect rgba(200,230,255,0.35)
    Note over Client,Info: 푸드트럭 정보 업데이트 (서비스 지역·가능일자 동기화)
    Client->>Ctrl: PUT /{foodTruckId} (UpdateFoodTruckInfoRequest)
    Ctrl->>Svc: updateMyFoodTruckInfo(ownerId, foodTruckId, request)
    Svc->>Info: updateMyFoodTruckInfo(owner, foodTruckId, request)
    Info->>Repo: findById(foodTruckId)
    Repo-->>Info: FoodTruck
    Info->>Info: validateOwner(), validateApprovedStatus()
    Info->>Info: syncServiceAreas(...)
    Info->>Info: syncAvailableDates(...)
    Info->>Repo: save(foodTruck)
    Info-->>Svc: foodTruckId
    Svc-->>Ctrl: FoodTruckIdResponse
    Ctrl-->>Client: 200 OK
    end

    rect rgba(255,230,200,0.35)
    Note over Client,S3: 이미지 삭제 (S3)
    Client->>Ctrl: DELETE /{foodTruckId}/images (DeleteFoodTruckImagesRequest)
    Ctrl->>Svc: deleteFoodTruckImagesFromS3(ownerId, foodTruckId, request)
    Svc->>Img: deleteFoodTruckImagesFromS3(owner, foodTruckId, request)
    Img->>Repo: findById(foodTruckId)
    Repo-->>Img: FoodTruck
    Img->>Img: validateOwner(owner)
    loop 이미지별
        Img->>Img: extractKeyFromUrl(imageUrl)
        Img->>S3: delete(s3Key)
    end
    Img-->>Svc: void
    Svc-->>Ctrl: 204 No Content
    Ctrl-->>Client: 204 No Content
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • 집중 검토 포인트:
    • FoodTruckInfoService의 소유권·승인 검증 및 syncServiceAreas/syncAvailableDates의 경계조건과 트랜잭션 안전성
    • 임베디드 FoodTruckInfo 도입에 따른 JPA 매핑(필드명 변경) 및 기존 쿼리/마이그레이션 영향
    • 값 객체(Enum)들의 JsonCreator/예외 경로 변경(역직렬화 에러 메시지/코드)
    • S3 이미지 삭제: CDN URL→S3 키 추출 로직과 예외/무결성 처리
    • 메뉴 가격 타입 변경으로 인한 소비자(프론트/내부) 영향
    • 삭제된 FoodTruckCommandService 삭제로 인한 참조 누락 여부

Possibly related PRs

Suggested reviewers

  • ksg1227

Poem

🐰
당근 한 입에 코드를 다듬었네,
정보는 작은 상자에 깔끔히 들어가고,
사진은 하늘(=S3)로 보내고 키는 찾아냈네.
바삭한 변경, 한 바퀴 폴짝—축하해요! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed PR 제목 "[FEAT] 푸드트럭 정보 등록/수정 api 구현"은 이 PR의 주요 목표를 명확하고 구체적으로 설명합니다. 제목은 새로운 푸드트럭 정보 등록 및 수정 API 구현이라는 핵심 변경 사항을 정확히 반영하며, 스캔할 때 개발자가 이해하기 쉬운 간결한 표현을 사용합니다. 이미지 삭제 API와 리팩토링 작업도 포함되어 있지만, 제목은 주요 기능에 초점을 맞추고 있어 적절합니다.
Linked Issues Check ✅ Passed PR의 코드 변경 사항이 연결된 이슈 #49의 모든 주요 코딩 목표를 충족합니다: (1) 소유주 및 승인 상태 검증은 FoodTruckService, FoodTruck 도메인 모델에서 구현 [#49]; (2) 푸드트럭 정보 생성 및 갱신은 새로운 FoodTruckInfoService와 FoodTruckInfo 값 객체에서 처리 [#49]; (3) 노출 상태 변경 제어는 canChangeViewedStatus 플래그와 permitChangeViewStatus(), changeViewedStatus() 메서드로 구현 [#49]; (4) 서비스 지역 동기화는 FoodTruckInfoService.syncServiceAreas()에서 추가/제거 로직 구현 [#49]; (5) 운영 날짜 동기화는 FoodTruckInfoService.syncAvailableDates()에서 기존 데이터 교체 방식으로 구현 [#49]; (6) 예외 처리는 ErrorCode 추가와 EntityNotFoundException 활용으로 구현 [#49].
Out of Scope Changes Check ✅ Passed 변경 사항의 대부분은 PR 목표 문서의 "리팩토링/기타 변경" 섹션에 명시된 범위 내에 있습니다. FoodTruckCommandService에서 FoodTruckInfoService로의 이름 변경 및 FoodTruckInfo VO 분리가 중심이며, 이에 따른 모든 응답 DTO 업데이트(MemberReservationDetailResponse, SavedFoodTruckResponse, MyFoodTruckResponse 등), Enum @JsonCreator 추가, 그리고 Menu.parsingMenuPrice() 제거 및 FoodTruckMenuResponse의 가격 타입 변경이 뒤따릅니다. 또한 application-local.yml의 ddl-auto 설정 변경과 MyFoodTruckMenuService의 S3 삭제 주석 처리는 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/#49-foodtruck-put-info

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (21)
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DeleteFoodTruckImagesRequest.java (1)

8-12: 추가 검증 로직을 고려해보세요.

현재 최소 1개의 URL만 검증하고 있습니다. 다음 사항들을 고려해보세요:

  • 최대 개수 제한 추가 (DoS 방지)
  • 리스트 내 빈 문자열 또는 null 검증
  • URL 형식 검증 (@Pattern 또는 커스텀 validator 사용)

예시:

 public record DeleteFoodTruckImagesRequest(
         @Schema(description = "삭제할 이미지 URL 목록", example = "[\"https://example.com/image1.jpg\", \"https://example.com/image2.jpg\"]")
-        @Size(min = 1, message = "최소 1개의 이미지 URL을 제공해야 합니다.")
+        @Size(min = 1, max = 20, message = "이미지 URL은 1개 이상 20개 이하로 제공해야 합니다.")
+        @NotEmpty
         List<String> imageUrls
 ) {
 }
src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java (1)

75-84: 트랜잭션 경계를 명확히 하세요.

이 메서드가 데이터베이스 조회는 하지만 업데이트는 하지 않으므로, S3 삭제 실패 시 롤백할 DB 변경사항이 없습니다. 하지만 메서드의 트랜잭션 동작을 명확히 문서화하거나 @Transactional(readOnly = true)를 고려해보세요.

src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckIdResponse.java (1)

3-5: API 문서화를 위한 Swagger 어노테이션 추가를 고려하세요.

다른 Response DTO들과 일관성을 위해 @Schema 어노테이션을 추가하는 것이 좋습니다.

+import io.swagger.v3.oas.annotations.media.Schema;
+
 public record FoodTruckIdResponse(
+        @Schema(description = "푸드트럭 식별자", example = "1")
         Long foodTruckId
 ) {
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java (1)

21-29: @JsonCreator 구현이 올바르게 추가되었습니다.

문자열에서 enum으로 역직렬화하는 기능이 적절하게 구현되었습니다. 에러 처리도 적절합니다.

현재는 3개 값이라 문제없지만, 향후 enum 값이 증가할 경우를 대비해 static Map 캐싱을 고려할 수 있습니다:

+    private static final Map<String, PaymentMethod> VALUE_MAP = 
+        Arrays.stream(values())
+            .collect(Collectors.toMap(PaymentMethod::getValue, Function.identity()));
+
     @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
     public static PaymentMethod from(String value) {
-        for (PaymentMethod method : PaymentMethod.values()) {
-            if (method.getValue().equals(value)) {
-                return method;
-            }
-        }
-        throw new DomainRuleException(ErrorCode.PAYMENT_METHOD_MISMATCH);
+        PaymentMethod method = VALUE_MAP.get(value);
+        if (method == null) {
+            throw new DomainRuleException(ErrorCode.PAYMENT_METHOD_MISMATCH);
+        }
+        return method;
     }
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java (1)

26-34: JsonCreator 입력 안전성 보강(Null/공백 처리) 및 직렬화 일관성 확보 권장

  • 현재 from(String value)는 null/공백 입력에 대한 방어가 없어 NPE 또는 불필요한 반복을 유발할 수 있습니다.
  • 직렬화 시 enum 이름이 아닌 사람 친화적 value를 내보내려면 @jsonvalue 지정이 필요합니다(최근 Lombok/Jackson 상호작용 변화로 접근자에 자동 전파가 되지 않을 수 있음). Based on learnings

다음 변경을 권장합니다:

@@
-    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
-    public static AvailableQuantity from(String value) {
-        for (AvailableQuantity quantity : AvailableQuantity.values()) {
-            if (quantity.getValue().equals(value)) {
-                return quantity;
-            }
-        }
-        throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
-    }
+    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+    public static AvailableQuantity from(String value) {
+        if (value == null) {
+            throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
+        }
+        final String v = value.trim();
+        for (AvailableQuantity quantity : AvailableQuantity.values()) {
+            if (quantity.getValue().equals(v)) {
+                return quantity;
+            }
+        }
+        throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
+    }

추가로(파일 내 다른 위치에 적용):

@@
-    private final String value;
+    @com.fasterxml.jackson.annotation.JsonValue
+    private final String value;

해당 enum의 JSON 직렬화 결과가 클라이언트 계약(예: "50인분 미만")과 일치하는지, 그리고 null/공백 입력이 실제 요청에서 발생할 수 있는지 확인 부탁드립니다. 최근 Lombok 1.18.4x의 Jackson 관련 변경으로 기존 동작과 달라질 수 있습니다. Based on learnings

src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java (1)

47-47: 이름 중복 체크의 현실 시나리오 보강(대소문자/자기 자신 제외 조건)

현재 existsByFoodTruckInfo_Name(String name)

  • 대소문자/공백 변형에 동일 이름으로 간주하지 못할 수 있고,
  • 수정 시 자기 자신의 레코드를 제외하지 못해 항상 중복으로 판단되는 문제가 생길 수 있습니다.

업데이트 시나리오를 위해 자기 자신 제외 메서드와(선택) 대소문자 무시 메서드를 추가하는 것을 권장합니다:

 interface FoodTruckRepository extends JpaRepository<FoodTruck, Long>, FoodTruckSearchRepository {
@@
-    boolean existsByFoodTruckInfo_Name(String name);
+    boolean existsByFoodTruckInfo_Name(String name);
+    boolean existsByFoodTruckInfo_NameAndFoodTruckIdNot(String name, Long foodTruckId);
+    boolean existsByFoodTruckInfo_NameIgnoreCase(String name);
  • 비즈니스 규칙상 대소문자/공백/특수문자 정규화가 필요한지 확인 부탁드립니다.
  • DB 차원에서 유니크 제약(또는 lower(name) 인덱스) 필요 여부도 검토 부탁드립니다.
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java (1)

28-36: JsonCreator 입력 정규화 및 직렬화 보장(@jsonvalue) 권장

  • from(String value)에 null/공백 방어와 trim이 없어 미묘한 파싱 실패가 날 수 있습니다.
  • Enum 직렬화 시 사람이 읽는 value를 내보내려면 @jsonvalue 지정이 안전합니다(최근 Lombok/Jackson 변화 고려). Based on learnings
@@
-    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
-    public static MenuCategory from(String value) {
-        for (MenuCategory category : MenuCategory.values()) {
-            if (category.getValue().equals(value)) {
-                return category;
-            }
-        }
-        throw new DomainRuleException(ErrorCode.MENU_CATEGORY_MISMATCH);
-    }
+    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
+    public static MenuCategory from(String value) {
+        if (value == null) {
+            throw new DomainRuleException(ErrorCode.MENU_CATEGORY_MISMATCH);
+        }
+        final String v = value.trim();
+        for (MenuCategory category : MenuCategory.values()) {
+            if (category.getValue().equals(v)) {
+                return category;
+            }
+        }
+        throw new DomainRuleException(ErrorCode.MENU_CATEGORY_MISMATCH);
+    }

파일 내 다른 위치에 적용:

@@
-    private final String value;
+    @com.fasterxml.jackson.annotation.JsonValue
+    private final String value;

다른 Enum(AvailableQuantity/NeedElectricity/PaymentMethod)도 동일 패턴으로 정규화/직렬화가 일관되게 적용되는지 확인 부탁드립니다. Based on learnings

src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java (1)

8-12: 엔티티에 @Builder/@AllArgsConstructor 공개 노출은 식별자 세팅 오용 위험

엔티티에 공개 Builder와 공개 AllArgsConstructor가 있으면 foodTruckServiceAreaId까지 세팅된 객체를 생성/영속 시도하는 실수가 발생하기 쉽습니다.

Builder/AllArgsConstructor 접근 제한을 권장합니다. 정적 팩토리로만 생성 경로를 노출하세요.

-@Builder
+@Builder(access = AccessLevel.PRIVATE)
@@
-@AllArgsConstructor
+@AllArgsConstructor(access = AccessLevel.PROTECTED)

정적 팩토리 createFoodTruckServiceArea는 그대로 공개로 유지하면 됩니다.

서비스 계층에서 직접 Builder나 AllArgsConstructor를 호출하는 사용처가 없는지 확인 부탁드립니다.

src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java (1)

68-72: S3 삭제 오퍼레이션: 부분 실패/멱등성 피드백 고려

외부 시스템 호출은 부분 실패 가능성이 있으므로, 호출 결과(성공/실패 키 리스트)를 서비스에서 받아 컨트롤러까지 전달하거나 최소한 로깅/모니터링 메트릭으로 남기는 것을 권장합니다. 재시도 시 멱등하게 동작하는지도 확인 필요합니다.

  • 실패 키 목록 반환 타입 도입(예: 삭제 성공/실패 분리).
  • 네트워크 타임아웃/재시도/백오프 정책 확인.
src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java (1)

110-123: DELETE 응답은 204 No Content 권장, DELETE 바디 사용 주의

  • 현재 BaseResponse.ok(null)로 200을 반환합니다. 리소스 삭제 작업에는 204를 권장합니다.
  • 일부 클라이언트/프록시는 DELETE 바디 지원이 미흡할 수 있어, 필요 시 POST /images:delete 같은 액션 엔드포인트도 고려해보세요.

BaseResponse 유틸에 204 빌더가 없다면 ResponseEntity.noContent()를 직접 반환하도록 변경을 검토하세요:

-    public BaseResponse<Void> deleteFoodTruckImagesFromS3( ... ) {
-        foodTruckService.deleteFoodTruckImagesFromS3(ownerId, foodTruckId, request);
-        return BaseResponse.ok(null);
-    }
+    public org.springframework.http.ResponseEntity<Void> deleteFoodTruckImagesFromS3( ... ) {
+        foodTruckService.deleteFoodTruckImagesFromS3(ownerId, foodTruckId, request);
+        return org.springframework.http.ResponseEntity.noContent().build();
+    }

클라이언트가 DELETE 바디를 문제없이 전송할 수 있는지(특히 API 게이트웨이/프록시 구성) 확인 부탁드립니다.

src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java (2)

52-56: createEmptyFoodTruckInfo: 이름 정규화(공백 트리밍) 최소 보강 권장

엔티티 임베더블에 들어가는 name은 키 성격이 강하므로, 최소한 공백 트리밍은 도메인 계층에서 보장하는 편이 안전합니다(상위 DTO/Validator와 중복되더라도 무해).

-    public static FoodTruckInfo createEmptyFoodTruckInfo(String name) {
+    public static FoodTruckInfo createEmptyFoodTruckInfo(String name) {
+        final String n = name == null ? null : name.trim();
         return FoodTruckInfo.builder()
-                .name(name)
+                .name(n)
                 .build();
     }

DTO 레벨에서 @NotBlank 등 검증이 이미 충분한지 확인 부탁드립니다. 중복이라면 이 변경은 선택 사항입니다.


58-84: updateFoodTruckInfo: 값 객체 불변성/정규화 관점 보강

  • PhotoUrlList, MenuCategoryList가 불변을 보장하면 좋습니다(외부 리스트 참조 누수 방지).
  • 문자열 필드 최소 trim 적용(특히 name/phoneNumber/activeTime/operatingInfo/option).
  • timeDiscussRequired는 기본값(false)로 강제하거나 null 허용 의도를 명확히 해두는 것이 좋습니다.
-        this.name = name;
-        this.description = description;
-        this.phoneNumber = phoneNumber;
-        this.activeTime = activeTime;
-        this.timeDiscussRequired = timeDiscussRequired;
-        this.foodTruckPhotoList = foodTruckPhotoList;
-        this.menuCategoryList = menuCategoryList;
+        this.name = name == null ? null : name.trim();
+        this.description = description == null ? null : description.trim();
+        this.phoneNumber = phoneNumber == null ? null : phoneNumber.trim();
+        this.activeTime = activeTime == null ? null : activeTime.trim();
+        this.timeDiscussRequired = timeDiscussRequired; // 기본값 정책 고려
+        this.foodTruckPhotoList = foodTruckPhotoList;   // 내부에서 불변 보장되는지 확인
+        this.menuCategoryList = menuCategoryList;       // 내부에서 불변 보장되는지 확인
         this.availableQuantity = availableQuantity;
         this.needElectricity = needElectricity;
         this.paymentMethod = paymentMethod;
-        this.operatingInfo = operatingInfo;
-        this.option = option;
+        this.operatingInfo = operatingInfo == null ? null : operatingInfo.trim();
+        this.option = option == null ? null : option.trim();
  • PhotoUrlList.of(...), MenuCategoryList.of(...)가 불변 복사/검증을 수행하는지 확인 부탁드립니다.
  • 이름 컬럼 길이/인덱스(중복 체크 성능)를 스키마에 반영했는지도 점검 바랍니다.
src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (1)

232-237: 이미지 삭제 API 에러셋 점검 제안

S3 삭제는 “존재하지 않는 키”도 멱등하게 처리하는 경우가 많지만, URL 검증(자사 CDN 도메인/키 패턴) 실패 시를 위한 INVALID_IMAGE_URL 류 코드가 있다면 함께 노출하는 것을 권장합니다. 현재 체계에 없다면 무시해도 됩니다.

src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java (1)

33-41: FoodTruckInfo 경유 접근 시 NPE 방어

foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl() 경로에서 중간 객체가 null일 수 있습니다(초기 데이터/마이그레이션 중). 아래처럼 널 방어를 권장합니다.

- FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo();
- String mainImageUrl = foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl();
+ FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo();
+ String mainImageUrl = (foodTruckInfo != null
+        && foodTruckInfo.getFoodTruckPhotoList() != null)
+        ? foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl()
+        : null;
src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java (2)

41-60: 응답 매핑 시 N+1 가능성

FoodTruckResponse.of(ft, ...) 내부에서 LAZY 연관 접근이 있다면 페이지 단위 N+1이 발생할 수 있습니다. 검색 리포지토리에서 fetch join/projection으로 필요한 필드만 조회하거나 DTO projection으로 내려주는 것을 권장합니다.


102-135: 서비스 지역 동기화: null‑safe 및 벌크 쿼리로 최적화

  • requestedRegionIds 가 null이면 NPE. 빈 집합으로 간주하면 “전체 제거”가 자연스럽습니다.
  • 추가분 조회/삭제를 ID 단건 반복으로 처리 → 벌크 조회/삭제로 N+1 완화.

예시 리팩터:

-    private void syncServiceAreas(FoodTruck foodTruck, Set<Long> requestedRegionIds) {
-        Set<Long> currentRegionIds = foodTruckServiceAreaRepository.findAllByFoodTruckId(foodTruck.getFoodTruckId()).stream()
+    private void syncServiceAreas(FoodTruck foodTruck, Set<Long> requestedRegionIds) {
+        Set<Long> requested = requestedRegionIds == null ? Set.of() : requestedRegionIds;
+        Set<Long> currentRegionIds = foodTruckServiceAreaRepository.findAllByFoodTruckId(foodTruck.getFoodTruckId()).stream()
                 .map(FoodTruckServiceArea::getRegion)
                 .map(Region::getRegionId)
                 .collect(Collectors.toSet());
 
         // 추가할 지역
-        Set<Long> regionsToAdd = new HashSet<>(requestedRegionIds);
+        Set<Long> regionsToAdd = new HashSet<>(requested);
         regionsToAdd.removeAll(currentRegionIds);
 
         // 제거할 지역
         Set<Long> regionsToRemove = new HashSet<>(currentRegionIds);
-        regionsToRemove.removeAll(requestedRegionIds);
+        regionsToRemove.removeAll(requested);
 
         // 추가
         if(!regionsToAdd.isEmpty()) {
-            List<FoodTruckServiceArea> serviceAreasToAdd = regionsToAdd.stream()
-                    .map(regionId -> FoodTruckServiceArea.createFoodTruckServiceArea(regionRepository.findById(regionId)
-                            .orElseThrow(() -> new EntityNotFoundException(ErrorCode.REGION_NOT_FOUND)), foodTruck))
-                    .toList();
+            List<Region> regions = regionRepository.findAllById(regionsToAdd);
+            if (regions.size() != regionsToAdd.size()) throw new EntityNotFoundException(ErrorCode.REGION_NOT_FOUND);
+            List<FoodTruckServiceArea> serviceAreasToAdd = regions.stream()
+                    .map(region -> FoodTruckServiceArea.createFoodTruckServiceArea(region, foodTruck))
+                    .toList();
             foodTruckServiceAreaRepository.saveAll(serviceAreasToAdd);
         }
 
         // 제거
         if(!regionsToRemove.isEmpty()) {
-            List<FoodTruckServiceArea> serviceAreasToRemove = regionsToRemove.stream()
-                    .map(regionId -> foodTruckServiceAreaRepository.findByFoodTruckIdAndRegionId(foodTruck.getFoodTruckId(), regionId)
-                            .orElseThrow(() -> new EntityNotFoundException(ErrorCode.FOOD_TRUCK_SERVICE_AREA_NOT_FOUND)))
-                    .toList();
-
-            foodTruckServiceAreaRepository.deleteAll(serviceAreasToRemove);
+            foodTruckServiceAreaRepository.deleteAllByFoodTruckIdAndRegionIdIn(
+                    foodTruck.getFoodTruckId(), regionsToRemove);
         }
     }

리포지토리에 deleteAllByFoodTruckIdAndRegionIdIn(Long, Collection<Long>) 메서드가 없다면 추가 구현이 필요합니다.

src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java (1)

146-157: 노출 상태 변경 시 승인 상태 가드 적용 여부 확인

changeViewedStatus는 동일 상태 전이 및 플래그만 검사합니다. 비즈니스 규칙상 “승인된 푸드트럭만 노출 상태 변경 가능”이라면 여기서도 validateApprovedStatus()를 호출해 도메인 레벨에서 보장하는 것을 권장합니다(서비스 누락 시 방어).

     public void changeViewedStatus(FoodTruckViewedStatus targetViewedStatus) {
+        validateApprovedStatus();
         if(this.foodTruckViewedStatus == targetViewedStatus) {
             throw new DomainRuleException(ErrorCode.INVALID_FOOD_TRUCK_STATUS_TRANSITION);
         }
         if(!this.canChangeViewedStatus) {
             throw new DomainRuleException(ErrorCode.FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED,
                     new IllegalArgumentException("최소 1번의 정보 기입 후에만 푸드트럭 노출 상태를 변경할 수 있습니다."));
         }
         this.foodTruckViewedStatus = targetViewedStatus;
     }
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java (4)

25-28: 전화번호 형식 제약 추가 제안

데이터 무결성을 위해 한국 번호 형식을 검증해주세요(지역번호/휴대폰, 국제 +82 허용 등).

예시 diff(완화된 패턴):

         @Schema(description = "푸드트럭 전화번호", example = "010-1234-5678")
-        @NotBlank(message = "푸드트럭 전화번호는 필수 입력 값입니다.")
+        @Pattern(
+                regexp = "^(?:\\+82-?0?\\d{1,2}|0\\d{1,2})-\\d{3,4}-\\d{4}$",
+                message = "전화번호는 '010-1234-5678' 형식이어야 합니다."
+        )
+        @NotBlank(message = "푸드트럭 전화번호는 필수 입력 값입니다.")
         String phoneNumber,

원하시면 지역번호/특정 포맷만 허용하도록 더 엄격한 정규식도 제공하겠습니다.


29-33: activeTime 의미 검증(시작<종료) 추가 권장

정규식은 형식만 검증합니다. 시작 시간이 종료 시간보다 빠른지 검증하는 커스텀 제약(예: @ValidTimeRange) 추가를 권장합니다.

적용 예:

  • 문자열 분리 후 LocalTime.parse로 비교
  • 야간 영업(예: 22:00-02:00)을 허용할지 정책 정의 필요(허용 시 “다음날” 의미를 반영한 비교 로직 필요)
    원하시면 @ValidTimeRange 애너테이션과 Validator 코드까지 드리겠습니다.

56-63: 오류 메시지 용어 일치(‘전기 필요 여부’)

도메인 용어 변경에 맞춰 메시지를 정정해 주세요.

-        @NotNull(message = "전기 사용 여부는 필수 입력 값입니다.")
+        @NotNull(message = "전기 필요 여부는 필수 입력 값입니다.")

76-79: 사진 URL 보안/정합성 강화(+ Swagger 설명 보완)

  • https 스킴 강제 및(선택) 사내 S3 버킷 도메인 화이트리스트를 권장합니다.
  • 메뉴 이미지는 제외됨을 스키마 설명에 명시해 주세요(운영/책임 분리 근거 강화).

예시 diff(https 강제 + 설명 보강 + 예시 수정, 요소 제약에 URL 패턴 추가하려면 Bean Validation 2.0 컨테이너 요소 중복 애너테이션 지원 필요):

-        @Schema(description = "푸드트럭 사진 URL 리스트", example = "[\"http://image1.png\", \"http://image2.png\"]")
+        @Schema(description = "푸드트럭 사진 URL 리스트 (메뉴 이미지 제외)", example = "[\"https://image1.png\", \"https://image2.png\"]")
         @NotNull(message = "푸드트럭 사진은 필수 입력 값입니다.")
         @Size(min = 1, max = 9, message = "푸드트럭 사진은 최소 1개 최대 9개까지 등록 가능합니다.")
-        List<@NotBlank String> photoUrls,
+        List<@NotBlank @Pattern(regexp = "^https://.+$", message = "사진 URL은 https 스킴이어야 합니다.") String> photoUrls,

버킷 도메인 화이트리스트를 적용하려면 정규식을 ‘^https://(cdn\.)?your-bucket\.s3\.[-a-z0-9]+\.amazonaws\.com/.+’ 등으로 교체하세요.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f46c320 and c8f2e44.

📒 Files selected for processing (31)
  • src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java (4 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java (4 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/MenuCategory.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PaymentMethod.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DeleteFoodTruckImagesRequest.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckIdResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java (1 hunks)
  • src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (2 hunks)
  • src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (2 hunks)
  • src/main/resources/application-local.yml (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/konkuk/chacall/domain/foodtruck/application/command/FoodTruckCommandService.java
🧰 Additional context used
🧬 Code graph analysis (5)
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java (1)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java (1)
  • Builder (14-158)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java (4)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java (1)
  • Builder (9-39)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java (1)
  • Builder (8-35)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckDocument.java (1)
  • Builder (7-37)
src/main/java/konkuk/chacall/domain/member/domain/SavedFoodTruck.java (1)
  • Builder (9-36)
src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java (2)
src/main/java/konkuk/chacall/global/common/exception/EntityNotFoundException.java (1)
  • EntityNotFoundException (6-11)
src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java (1)
  • RequiredArgsConstructor (16-73)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java (2)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java (1)
  • Builder (9-39)
src/main/java/konkuk/chacall/domain/member/domain/SavedFoodTruck.java (1)
  • Builder (9-36)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java (3)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruckServiceArea.java (1)
  • Builder (8-35)
src/main/java/konkuk/chacall/domain/member/domain/SavedFoodTruck.java (1)
  • Builder (9-36)
src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java (1)
  • Entity (10-74)
🔇 Additional comments (22)
src/main/resources/application-local.yml (1)

13-13: 적절한 변경입니다.

로컬 개발 환경에서 데이터를 보존하기 위해 create에서 update로 변경한 것은 타당합니다. 다만, 운영 환경 배포 전에 스키마 마이그레이션을 별도로 검증하는 것을 권장합니다.

src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (2)

81-85: 새로운 에러 코드들이 잘 정의되었습니다.

추가된 에러 코드들이 기존 패턴을 따르고 있으며, 코드 번호도 충돌 없이 순차적으로 할당되었습니다. 메시지도 명확합니다.


108-111: 새로운 카테고리 시작이 적절합니다.

FoodTruckServiceArea를 위한 새로운 에러 코드 카테고리(150xxx)가 추가되었습니다. 향후 이 도메인에 대한 추가 에러 코드가 필요할 경우 이 범위를 사용할 수 있습니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (1)

45-51: FoodTruckInfo 도입에 따른 적절한 리팩토링입니다.

FoodTruck 엔티티에서 FoodTruckInfo 값 객체를 통해 데이터에 접근하도록 변경되었습니다. 이는 PR의 목표와 일치하며, 도메인 모델의 캡슐화를 개선합니다.

src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckResponse.java (1)

28-39: FoodTruckInfo를 통한 일관된 데이터 접근입니다.

모든 푸드트럭 정보(이름, 사진, 설명, 메뉴 카테고리)를 FoodTruckInfo 값 객체를 통해 접근하도록 변경되었습니다. 이는 도메인 모델의 일관성을 유지하며, 향후 변경 시 영향 범위를 제한합니다.

src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java (1)

27-37: FoodTruckInfo 접근으로 일관성 유지

다른 응답 DTO들과 동일하게 FoodTruckInfo를 통해 푸드트럭 이름에 접근하도록 변경되었습니다. 전체 코드베이스에서 일관된 패턴을 유지합니다.

src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java (1)

45-51: FoodTruckInfo 리팩토링이 완료되었습니다.

모든 응답 DTO에서 FoodTruckInfo를 통한 데이터 접근으로 일관되게 변경되었습니다. 이는 도메인 모델의 캡슐화를 강화하고, 향후 유지보수성을 높입니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java (1)

22-27: FoodTruckInfo 추출 패턴이 효율적입니다.

foodTruckInfo를 한 번만 추출하여 여러 필드 접근에 재사용하는 방식이 좋습니다. 리팩토링 목표와 일치하며 코드가 명확합니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java (1)

36-42: FoodTruckInfo 사용 패턴이 일관성 있게 적용되었습니다.

다른 Response DTO들과 동일한 패턴으로 리팩토링되어 코드베이스 전반에 일관성이 유지됩니다.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationHistoryResponse.java (1)

28-33: 리팩토링이 일관성 있게 적용되었습니다.

FoodTruckInfo 접근 패턴이 다른 DTO들과 동일하게 구현되어 있습니다.

src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java (1)

62-62: 전기 사용 용어 변경이 올바르게 반영되었습니다.

PR 목표에 명시된 대로 '가능/불가능'에서 '필요/불필요'로 용어가 변경되어 Swagger 문서가 업데이트되었습니다.

src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/infra/FoodTruckSearchRepositoryImpl.java (2)

37-37: FoodTruckInfo 별칭 추가가 적절합니다.

중첩된 객체 경로를 명확하게 표현하기 위해 별칭을 추가한 것이 좋습니다.


78-103: 검색 조건이 FoodTruckInfo 구조에 맞게 업데이트되었습니다.

모든 predicate가 중첩된 foodTruckInfo 필드를 올바르게 참조하도록 변경되었습니다. QueryDSL 쿼리 구조가 정확합니다.

src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java (1)

28-31: 원본 검토 의견의 우려 사항이 실제 코드 사용 패턴과 맞지 않습니다.

검색 결과를 확인한 결과:

  • findByFoodTruckIdAndRegionId 메서드는 FoodTruckInfoService.java에서 오직 삭제 작업에만 사용됩니다
  • 반환된 FoodTruckServiceArea 객체는 deleteAll()로 바로 전달되므로 region 필드에 접근하지 않습니다
  • 따라서 N+1 쿼리 문제는 이 사용처에서는 발생하지 않습니다

원본 검토 의견에서 언급한 "사용처에서 region 정보가 필요한 경우 N+1 문제" 우려는 현재 코드에 적용되지 않습니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java (1)

59-66: 쓰기 트랜잭션 범위 적절 — canChangeViewedStatus 토글 확인 필요

메서드 수준 @Transactional로 클래스 기본(readOnly=true)을 올바르게 무시했습니다. 다만 요구사항상 “정보 입력 이후에만 노출 상태 변경 가능”을 위해 업데이트 시 FoodTruck.permitChangeViewStatus() 호출(또는 동등 로직)이 수행되는지 확인 필요합니다.

FoodTruckInfoService.updateMyFoodTruckInfo(...)에서 정보 입력 성공 시 permitChangeViewStatus()가 보장되는지 확인 부탁드립니다.

src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java (1)

100-108: PUT 엔드포인트 시그니처 적절 — 스웨거 명세와 일치

요청/응답 DTO 연결과 서비스 위임이 명확합니다. 추가로 요청 DTO에 필드 유효성 애너테이션(길이, 패턴 등)이 충분한지만 점검하면 좋겠습니다.

요청 DTO의 필수 필드 및 길이 제약이 스키마에 반영되어 있는지 확인 부탁드립니다.

src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (1)

97-99: 뷰 상태 업데이트 에러셋 보강 LGTM

INVALID_FOOD_TRUCK_STATUS_TRANSITION, FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED 추가로 도메인 가드와 Swagger 문서가 일치합니다. 별도 이슈 없습니다.

src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java (2)

62-64: 이름 중복 체크 LGTM

쿼리 메서드를 임베디드 경로(foodTruckInfo.name)에 맞춰 사용하고 있어 일관적입니다.


66-88: 업데이트 가능 조건(승인 상태) 정책 확인 필요

validateApprovedStatus()로 “승인된 푸드트럭만 정보 수정 가능” 정책을 강제하고 있습니다. PR 설명의 “최소 1회 정보 기입 후 노출 상태 변경 가능” 요구와 함께, 승인 전 최초 정보 기입을 허용해야 하는지 비즈니스 의도 확인이 필요합니다. 현재 로직은 승인 전 기입을 막습니다.

src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java (3)

83-87: 노출 상태 변경 허용 플래그 로직 LGTM

정보 기입 성공 이후에만 변경 가능하도록 플래그를 세팅하는 의도가 코드에 반영되어 있습니다. 도메인 이벤트/리스너로도 추후 확장 가능해 보입니다.


88-116: 임베디드 FoodTruckInfo 위임 업데이트 LGTM

값 객체로 이관된 필드를 일괄 업데이트하며, 사진/카테고리 컬렉션은 래퍼(PhotoUrlList, MenuCategoryList)를 통해 일관성 있게 감쌉니다.


47-51: 원본 리뷰 댓글의 주요 주장이 부정확함

검증 결과, 코드에는 canChangeViewedStatus가 일관되게 사용되고 있으며, 리뷰에서 언급한 "PR 요약의 DB 컬럼 canChangeViewStatus"에 대한 근거를 찾을 수 없습니다. 전체 검색 결과에서 canChangeViewStatus(ed 없음) 변형은 나타나지 않습니다.

실제 코드는:

  • 필드명: canChangeViewedStatus (line 50, 85, 151)
  • 메서드명: permitChangeViewStatus() (line 84) — 이것이 실제 불일치입니다
  • @Column 주석 처리 상태 → Hibernate 기본 네이밍 전략 적용

마이그레이션/DDL 파일을 찾을 수 없어 실제 DB 컬럼명을 검증할 수 없습니다. 리뷰의 특정 DB 컬럼 불일치 주장은 확인 불가능합니다.

Likely an incorrect or invalid review comment.

Comment on lines +75 to +84
public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) {
FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId)
.orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_FOUND));

foodTruck.validateOwner(owner.getUserId());

request.imageUrls().stream()
.map(cdnUrlResolver::extractKeyFromUrl)
.forEach(s3Service::delete);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

부분 실패 시나리오에 대한 에러 처리를 개선하세요.

현재 구현은 여러 이미지를 삭제하는 과정에서 중간에 실패가 발생하면 나머지 삭제가 시도되지 않을 수 있습니다. 다음 사항을 고려해보세요:

  1. 모든 삭제 시도 후 실패 집계: 한 이미지 삭제 실패가 다른 이미지 삭제를 막지 않도록 처리
  2. 부분 실패 로깅: 어떤 이미지가 실패했는지 추적
  3. Invalid URL 처리: extractKeyFromUrl이 실패할 경우 적절한 에러 메시지 제공

예시:

 public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) {
     FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId)
             .orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_FOUND));
 
     foodTruck.validateOwner(owner.getUserId());
 
-    request.imageUrls().stream()
-            .map(cdnUrlResolver::extractKeyFromUrl)
-            .forEach(s3Service::delete);
+    List<String> failedUrls = new ArrayList<>();
+    request.imageUrls().forEach(url -> {
+        try {
+            String key = cdnUrlResolver.extractKeyFromUrl(url);
+            s3Service.delete(key);
+        } catch (Exception e) {
+            log.error("Failed to delete image: {}", url, e);
+            failedUrls.add(url);
+        }
+    });
+    
+    if (!failedUrls.isEmpty()) {
+        throw new BusinessException(ErrorCode.IMAGE_DELETION_FAILED, 
+            new IllegalStateException("일부 이미지 삭제 실패: " + failedUrls));
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) {
FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId)
.orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_FOUND));
foodTruck.validateOwner(owner.getUserId());
request.imageUrls().stream()
.map(cdnUrlResolver::extractKeyFromUrl)
.forEach(s3Service::delete);
}
public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) {
FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId)
.orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_FOUND));
foodTruck.validateOwner(owner.getUserId());
List<String> failedUrls = new ArrayList<>();
request.imageUrls().forEach(url -> {
try {
String key = cdnUrlResolver.extractKeyFromUrl(url);
s3Service.delete(key);
} catch (Exception e) {
log.error("Failed to delete image: {}", url, e);
failedUrls.add(url);
}
});
if (!failedUrls.isEmpty()) {
throw new BusinessException(ErrorCode.IMAGE_DELETION_FAILED,
new IllegalStateException("일부 이미지 삭제 실패: " + failedUrls));
}
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java
around lines 75–84, change the current bulk-delete to attempt every image
deletion independently: for each request.imageUrl(), first try to extract the S3
key and if extractKeyFromUrl fails catch that exception and record the URL +
reason; otherwise call s3Service.delete in a try-catch and on failure record the
URL + exception; after processing all URLs, if any failures were recorded, log
detailed failures and throw a BusinessException (or a custom
PartialFailureException) containing aggregated failure info so callers know
which URLs failed and why; ensure successful deletions continue despite failures
and that invalid URLs are reported clearly.

Comment on lines +90 to +101
private void syncAvailableDates(FoodTruck foodTruck, List<DateRangeRequest> dateRangeRequests) {
// 기존 운영 가능 날짜 삭제
availableDateRepository.deleteAllByFoodTruckId(foodTruck.getFoodTruckId());

// 새로운 운영 가능 날짜 추가
List<AvailableDate> newAvailableDates = dateRangeRequests.stream()
.map(request -> AvailableDate.createAvailableDate(request.startDate(), request.endDate(), foodTruck))
.toList();

availableDateRepository.saveAll(newAvailableDates);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

운영 가능 날짜 동기화: null/검증 누락

  • dateRangeRequests 가 null이면 NPE.
  • startDate > endDate 같은 비정상 구간에 대한 검증 없음.

아래처럼 null‑safe 처리 및 범위 검증을 권장합니다.

-    private void syncAvailableDates(FoodTruck foodTruck, List<DateRangeRequest> dateRangeRequests) {
+    private void syncAvailableDates(FoodTruck foodTruck, List<DateRangeRequest> dateRangeRequests) {
         // 기존 운영 가능 날짜 삭제
         availableDateRepository.deleteAllByFoodTruckId(foodTruck.getFoodTruckId());
 
         // 새로운 운영 가능 날짜 추가
-        List<AvailableDate> newAvailableDates = dateRangeRequests.stream()
-                .map(request -> AvailableDate.createAvailableDate(request.startDate(), request.endDate(), foodTruck))
+        List<DateRangeRequest> safe = dateRangeRequests == null ? List.of() : dateRangeRequests;
+        List<AvailableDate> newAvailableDates = safe.stream()
+                .peek(r -> {
+                    if (r.startDate() == null || r.endDate() == null || r.startDate().isAfter(r.endDate())) {
+                        throw new IllegalArgumentException("유효하지 않은 날짜 범위");
+                    }
+                })
+                .map(r -> AvailableDate.createAvailableDate(r.startDate(), r.endDate(), foodTruck))
                 .toList();
 
         availableDateRepository.saveAll(newAvailableDates);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void syncAvailableDates(FoodTruck foodTruck, List<DateRangeRequest> dateRangeRequests) {
// 기존 운영 가능 날짜 삭제
availableDateRepository.deleteAllByFoodTruckId(foodTruck.getFoodTruckId());
// 새로운 운영 가능 날짜 추가
List<AvailableDate> newAvailableDates = dateRangeRequests.stream()
.map(request -> AvailableDate.createAvailableDate(request.startDate(), request.endDate(), foodTruck))
.toList();
availableDateRepository.saveAll(newAvailableDates);
}
private void syncAvailableDates(FoodTruck foodTruck, List<DateRangeRequest> dateRangeRequests) {
// 기존 운영 가능 날짜 삭제
availableDateRepository.deleteAllByFoodTruckId(foodTruck.getFoodTruckId());
// 새로운 운영 가능 날짜 추가
List<DateRangeRequest> safe = dateRangeRequests == null ? List.of() : dateRangeRequests;
List<AvailableDate> newAvailableDates = safe.stream()
.peek(r -> {
if (r.startDate() == null || r.endDate() == null || r.startDate().isAfter(r.endDate())) {
throw new IllegalArgumentException("유효하지 않은 날짜 범위");
}
})
.map(r -> AvailableDate.createAvailableDate(r.startDate(), r.endDate(), foodTruck))
.toList();
availableDateRepository.saveAll(newAvailableDates);
}
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/application/info/FoodTruckInfoService.java
around lines 90 to 101, the syncAvailableDates method does not handle null
dateRangeRequests and lacks range validation; update it to first treat null as
an empty list (or return early) to avoid NPE, then validate each
DateRangeRequest ensuring startDate is non-null, endDate is non-null and
startDate is on-or-before endDate; for invalid entries either throw a clear
IllegalArgumentException with context or skip them and log a warning, and only
map and save the validated requests via availableDateRepository.saveAll.

Comment on lines +32 to +38
public static AvailableDate createAvailableDate(LocalDate startDate, LocalDate endDate, FoodTruck foodTruck) {
return AvailableDate.builder()
.startAt(startDate)
.endAt(endDate)
.foodTruck(foodTruck)
.build();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

날짜 유효성(시작 ≤ 종료) 검증 누락

기간 엔티티에서 startAt > endAt이 저장되면 검색/동기화 로직 전반이 깨질 수 있습니다.

정적 팩토리에서 사전 검증을 추가해 주세요:

-    public static AvailableDate createAvailableDate(LocalDate startDate, LocalDate endDate, FoodTruck foodTruck) {
-        return AvailableDate.builder()
+    public static AvailableDate createAvailableDate(LocalDate startDate, LocalDate endDate, FoodTruck foodTruck) {
+        if (startDate == null || endDate == null || startDate.isAfter(endDate)) {
+            throw new konkuk.chacall.global.common.exception.DomainRuleException(
+                    konkuk.chacall.global.common.exception.code.ErrorCode.INVALID_DATE_RANGE);
+        }
+        return AvailableDate.builder()
                 .startAt(startDate)
                 .endAt(endDate)
                 .foodTruck(foodTruck)
                 .build();
     }

해당 테이블에 (food_truck_id, start_at, end_at) 복합 인덱스 추가를 검토하면 기간 조회/동기화 성능에 도움이 됩니다.

🤖 Prompt for AI Agents
In src/main/java/konkuk/chacall/domain/foodtruck/domain/model/AvailableDate.java
around lines 32-38, the static factory createAvailableDate lacks validation that
startAt is not after endAt; add a precondition check (e.g., if
startDate.isAfter(endDate) throw new IllegalArgumentException with a clear
message) before building the entity (also guard against nulls if required), and
update/ add unit tests to cover the invalid-range case; additionally, review the
schema to add a composite index on (food_truck_id, start_at, end_at) to improve
period query/sync performance.

Comment on lines +23 to +25
@Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " +
"WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
List<FoodTruckServiceArea> findAllByFoodTruckId(Long foodTruckId);

Copy link
Copy Markdown

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

서비스 지역 동기화를 위한 쿼리 메서드가 추가되었습니다.

새로운 쿼리 메서드가 적절하게 추가되었습니다.

다만 이 메서드는 JOIN FETCH를 사용하지 않아, 이후 region 엔티티에 접근할 경우 N+1 문제가 발생할 수 있습니다. FoodTruckInfoService에서 이 메서드를 사용할 때 region 정보가 필요한지 확인해주세요.

만약 region 정보가 필요하다면 아래와 같이 수정하는 것을 권장합니다:

 @Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " +
+        "JOIN FETCH ftsa.region " +
         "WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
 List<FoodTruckServiceArea> findAllByFoodTruckId(Long foodTruckId);

다음 스크립트로 사용 패턴을 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# FoodTruckInfoService에서 findAllByFoodTruckId 사용 후 region 접근 여부 확인
rg -A 10 "findAllByFoodTruckId" --type=java -g "FoodTruckInfoService.java"

Length of output: 1492


쿼리에 JOIN FETCH를 추가하여 N+1 문제를 해결해야 합니다.

FoodTruckInfoService에서 이 메서드의 결과에 .map(FoodTruckServiceArea::getRegion)로 직접 region 엔티티에 접근하고 있습니다. 현재 쿼리는 JOIN FETCH를 사용하지 않아, 반환된 각 FoodTruckServiceArea마다 추가 쿼리가 발생하는 N+1 문제가 발생합니다.

아래와 같이 수정해주세요:

 @Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " +
+        "JOIN FETCH ftsa.region " +
         "WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
 List<FoodTruckServiceArea> findAllByFoodTruckId(Long foodTruckId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " +
"WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
List<FoodTruckServiceArea> findAllByFoodTruckId(Long foodTruckId);
@Query("SELECT ftsa FROM FoodTruckServiceArea ftsa " +
"JOIN FETCH ftsa.region " +
"WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
List<FoodTruckServiceArea> findAllByFoodTruckId(Long foodTruckId);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java
around lines 23 to 25, the JPQL query must eagerly fetch the associated region
to avoid N+1 when callers access getRegion(); modify the query to use JOIN FETCH
for ftsa.region (e.g., add "JOIN FETCH ftsa.region") so the Region is loaded
with the FoodTruckServiceArea results; keep the method signature the same and
ensure the JPQL returns distinct results if necessary.

Comment on lines +21 to +29
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static NeedElectricity from(String value) {
for (NeedElectricity needElectricity : NeedElectricity.values()) {
if (needElectricity.getValue().equals(value)) {
return needElectricity;
}
}
throw new DomainRuleException(ErrorCode.NEED_ELECTRICITY_MISMATCH);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

응답 직렬화 값 보장 필요(@jsonvalue) 및 입력 정규화

  • 현재 @JsonCreator로 역직렬화는 되지만, 직렬화 시에는 기본적으로 enum name(REQUIRED/NOT_REQUIRED) 이 내려갑니다. 클라이언트가 "필요/불필요"를 기대한다면 @JsonValuevalue를 반환하도록 해야 계약이 일치합니다.
  • 입력값 공백/대소문/전각·유니코드 변형에 대한 내성도 있으면 좋습니다.

예시 수정안:

 import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
@@
     private final String value;
@@
+    @JsonValue
+    public String getValue() {
+        return value;
+    }
@@
-    public static NeedElectricity from(String value) {
-        for (NeedElectricity needElectricity : NeedElectricity.values()) {
-            if (needElectricity.getValue().equals(value)) {
+    public static NeedElectricity from(String value) {
+        if (value == null) throw new DomainRuleException(ErrorCode.NEED_ELECTRICITY_MISMATCH);
+        final String normalized = value.trim();
+        for (NeedElectricity needElectricity : NeedElectricity.values()) {
+            if (needElectricity.getValue().equals(normalized)) {
                 return needElectricity;
             }
         }
         throw new DomainRuleException(ErrorCode.NEED_ELECTRICITY_MISMATCH);
     }

유사한 패턴의 다른 enum(메뉴 카테고리 등)도 동일하게 정렬해 주세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/NeedElectricity.java
around lines 21-29, the enum currently only provides a @JsonCreator for
deserialization but serializes as the enum name; add @JsonValue to the method
that returns the user-facing value so Jackson emits "필요"/"불필요" instead of
REQUIRED/NOT_REQUIRED, and harden the from(String) factory to tolerate nulls and
varied input by normalizing the argument (apply a null check, trim(), Unicode
normalization to NFKC, and case-folding via toLowerCase(Locale.ROOT) or similar)
before comparing against normalized enum values; throw the same
DomainRuleException on mismatch. Apply the same @JsonValue and
input-normalization pattern to other similar enums (menu categories, etc.) to
keep API contract consistent.

Comment on lines +17 to +24
@Schema(description = "푸드트럭 이름", example = "맛있는 푸드트럭")
@NotBlank(message = "푸드트럭 이름은 필수 입력 값입니다.")
String name,

@Schema(description = "푸드트럭 설명", example = "저희 푸드트럭은 신선한 재료로 만든 음식을 제공합니다.")
@NotBlank(message = "푸드트럭 설명은 필수 입력 값입니다.")
String description,

Copy link
Copy Markdown

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

텍스트 길이 제약(@SiZe max) DB 스키마와 정합성 확인 필요

name/description/operatingInfo/option은 최대 길이 제약이 없어 DB 컬럼 길이를 초과할 수 있습니다. 엔티티 @column(length=...)와 일치하도록 @SiZe(max=...)를 추가해 주세요.

점검 스크립트(엔티티 컬럼 길이 탐색):

확인 후 max 값을 알려주시면 정확한 @SiZe(max=...) 패치도 드리겠습니다.

Also applies to: 80-85


🏁 Script executed:

#!/bin/bash
# FoodTruck(또는 관련 VO/Entity) 내 컬럼 길이 주석/애너테이션 검색
fd -t f -e java | rg -nP -C3 '(class|@Entity)\s+FoodTruck|FoodTruckInfo'
rg -nP -C2 '@Column\(([^)]*length\s*=\s*\d+[^)]*)\)|String\s+name|String\s+description|operatingInfo|option|phone' --type=java

Length of output: 47273


DB 스키마와 일치하도록 @SiZe(max=...) 제약 추가 필요

검증 결과 다음과 같이 FoodTruckInfo 엔티티의 컬럼 길이와 UpdateFoodTruckInfoRequest의 유효성 검증이 불일치합니다:

  • name: @SiZe 제약 없음 (권장: max = 10, FoodTruckCreateRequest와 FoodTruckNameDuplicateCheckRequest 참고)
  • description: @SiZe 제약 없음 → DB는 length = 300
  • operatingInfo: @SiZe 제약 없음 → DB는 length = 800
  • option: @SiZe 제약 없음 → DB는 length = 800

각 필드에 다음을 추가하세요:

@Size(max = 10, message = "푸드트럭 이름은 최대 10자까지 입력 가능합니다.")
@Size(max = 300, message = "푸드트럭 설명은 최대 300자까지 입력 가능합니다.")
@Size(max = 800, message = "운영 정보는 최대 800자까지 입력 가능합니다.")
@Size(max = 800, message = "기타 옵션은 최대 800자까지 입력 가능합니다.")
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java
around lines 17 to 24, the DTO validation doesn't enforce the DB column length
constraints; add @Size annotations to match the FoodTruckInfo entity lengths:
annotate name with @Size(max = 10, message = "푸드트럭 이름은 최대 10자까지 입력 가능합니다."),
description with @Size(max = 300, message = "푸드트럭 설명은 최대 300자까지 입력 가능합니다."),
operatingInfo with @Size(max = 800, message = "운영 정보는 최대 800자까지 입력 가능합니다."), and
option with @Size(max = 800, message = "기타 옵션은 최대 800자까지 입력 가능합니다.") so
validation aligns with the DB schema.

Comment on lines +38 to +41
@Schema(description = "푸드트럭 서비스 가능 지역 ID 리스트", example = "[1, 2, 3]")
@Size(min = 1, max = 10, message = "서비스 가능 지역은 최소 1개 최대 10개 선택 가능합니다.")
Set<@NotNull Long> foodTruckServiceAreas,

Copy link
Copy Markdown

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

컬렉션 필드에 @NotNull 누락 및 중첩 DTO에 @Valid 누락 — 유효성 검증이 우회됩니다.

null 컬렉션은 @SiZe가 동작하지 않아 제약을 통과합니다. 또한 availableDates의 DateRangeRequest 내부 제약이 전파되지 않습니다. 필수 컬렉션에는 @NotNull을 추가하고, 중첩 DTO에는 @Valid를 추가해 주세요.

적용 diff:

@@
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.Valid;
@@
         @Schema(description = "푸드트럭 서비스 가능 지역 ID 리스트", example = "[1, 2, 3]")
-        @Size(min = 1, max = 10, message = "서비스 가능 지역은 최소 1개 최대 10개 선택 가능합니다.")
+        @NotNull(message = "서비스 가능 지역은 필수 입력 값입니다.")
+        @Size(min = 1, max = 10, message = "서비스 가능 지역은 최소 1개 최대 10개 선택 가능합니다.")
         Set<@NotNull Long> foodTruckServiceAreas,
@@
         @Schema(description = "메뉴 카테고리 리스트", example = "[\"한식\", \"분식\"]")
-        @Size(min = 1, max = 12, message = "메뉴 카테고리는 최소 1개 최대 12개 선택 가능합니다.")
+        @NotNull(message = "메뉴 카테고리는 필수 입력 값입니다.")
+        @Size(min = 1, max = 12, message = "메뉴 카테고리는 최소 1개 최대 12개 선택 가능합니다.")
         List<@NotNull MenuCategory> menuCategories,
@@
         @Schema(description = "운영 가능 날짜 리스트", example = "[{\"startDate\":\"2024.10.01\",\"endDate\":\"2024.10.10\"}]")
-        @Size(min = 1, max = 4, message = "운영 가능 날짜는 최소 1개, 최대 4개까지 등록 가능합니다.")
-        List<DateRangeRequest> availableDates,
+        @NotNull(message = "운영 가능 날짜는 필수 입력 값입니다.")
+        @Size(min = 1, max = 4, message = "운영 가능 날짜는 최소 1개, 최대 4개까지 등록 가능합니다.")
+        List<@Valid DateRangeRequest> availableDates,
@@
         @Schema(description = "푸드트럭 사진 URL 리스트", example = "[\"http://image1.png\", \"http://image2.png\"]")
-        @Size(min = 1, max = 9, message = "푸드트럭 사진은 최소 1개 최대 9개까지 등록 가능합니다.")
-        List<@NotBlank String> photoUrls,
+        @NotNull(message = "푸드트럭 사진은 필수 입력 값입니다.")
+        @Size(min = 1, max = 9, message = "푸드트럭 사진은 최소 1개 최대 9개까지 등록 가능합니다.")
+        List<@NotBlank String> photoUrls,

중첩 DTO 제약 존재 여부를 확인해 주세요. 아래 스크립트는 DateRangeRequest 선언과 필드 제약을 찾아봅니다.

Also applies to: 42-45, 72-75, 76-79, 3-7


🏁 Script executed:

#!/bin/bash
rg -nP -C3 '(class|record)\s+DateRangeRequest\b'
rg -nP -C2 '@NotNull|@Past|@Future|@Pattern' --type=java | rg -nP -C2 'DateRangeRequest|startDate|endDate'

Length of output: 922



---

🏁 Script executed:

```shell
#!/bin/bash
# Get full DateRangeRequest definition
cat src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/DateRangeRequest.java

Length of output: 564


🏁 Script executed:

#!/bin/bash
# Check if availableDates field in UpdateFoodTruckInfoRequest has @Valid
rg -nP -A2 -B2 'availableDates' src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java

Length of output: 370


컬렉션 필드에 @NotNull 누락 및 중첩 DTO에 @Valid 누락 — 유효성 검증이 우회됩니다.

null 컬렉션은 @SiZe가 동작하지 않아 제약을 통과합니다. DateRangeRequest에는 검증 제약이 전혀 없으며(startDate, endDate에 @NotNull, @past, @future 등 없음), availableDates에 @Valid가 없어서 중첩 검증이 실행되지 않습니다. 필수 컬렉션에는 @NotNull을 추가하고, availableDates에는 @Valid를 추가해 주세요.

적용 diff:

+import jakarta.validation.Valid;
@@
         @Schema(description = "푸드트럭 서비스 가능 지역 ID 리스트", example = "[1, 2, 3]")
+        @NotNull(message = "서비스 가능 지역은 필수 입력 값입니다.")
         @Size(min = 1, max = 10, message = "서비스 가능 지역은 최소 1개 최대 10개 선택 가능합니다.")
         Set<@NotNull Long> foodTruckServiceAreas,
@@
         @Schema(description = "메뉴 카테고리 리스트", example = "[\"한식\", \"분식\"]")
+        @NotNull(message = "메뉴 카테고리는 필수 입력 값입니다.")
         @Size(min = 1, max = 12, message = "메뉴 카테고리는 최소 1개 최대 12개 선택 가능합니다.")
         List<@NotNull MenuCategory> menuCategories,
@@
         @Schema(description = "운영 가능 날짜 리스트", example = "[{\"startDate\":\"2024.10.01\",\"endDate\":\"2024.10.10\"}]")
+        @NotNull(message = "운영 가능 날짜는 필수 입력 값입니다.")
         @Size(min = 1, max = 4, message = "운영 가능 날짜는 최소 1개, 최대 4개까지 등록 가능합니다.")
-        List<DateRangeRequest> availableDates,
+        List<@Valid DateRangeRequest> availableDates,
@@
         @Schema(description = "푸드트럭 사진 URL 리스트", example = "[\"http://image1.png\", \"http://image2.png\"]")
+        @NotNull(message = "푸드트럭 사진은 필수 입력 값입니다.")
         @Size(min = 1, max = 9, message = "푸드트럭 사진은 최소 1개 최대 9개까지 등록 가능합니다.")
         List<@NotBlank String> photoUrls,

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java
around lines 38-41, the collection and nested DTO validations are missing: add
@NotNull to the foodTruckServiceAreas collection declaration so null collections
cannot bypass @Size, add @Valid to the availableDates field so nested
DateRangeRequest objects are validated, and update the DateRangeRequest class to
include validation annotations on its fields (at minimum @NotNull on startDate
and endDate and appropriate temporal constraints such as
@Past/@Future/@FutureOrPresent or @PastOrPresent as required by the domain) so
those constraints are enforced during nested validation.

Comment on lines +224 to +231
UPDATE_FOOD_TRUCK_INFO(new LinkedHashSet<>(Set.of(
USER_NOT_FOUND,
USER_FORBIDDEN,
FOOD_TRUCK_NOT_FOUND,
FOOD_TRUCK_NOT_OWNED,
FOOD_TRUCK_NOT_APPROVED,
REGION_NOT_FOUND
))),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

UPDATE_FOOD_TRUCK_INFO의 에러 코드 누락 가능성(요청 바인딩/검증 계층)

요청 본문에서 Enum 문자열을 @JsonCreator로 파싱하다가 불일치 시 *_MISMATCH 예외(예: MENU_CATEGORY_MISMATCH, AVAILABLE_QUANTITY_MISMATCH, NEED_ELECTRICITY_MISMATCH, PAYMENT_METHOD_MISMATCH)가 발생할 수 있습니다. 해당 코드들이 여기 에러셋에 포함되지 않아 Swagger가 실제 응답 스펙을 충분히 반영하지 못합니다. 에러셋에 해당 항목들을 추가해 주세요.

🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java
around lines 224 to 231, the UPDATE_FOOD_TRUCK_INFO response set omits enum
parsing/validation mismatch errors from the request binding layer; add the
relevant *_MISMATCH error constants (e.g., MENU_CATEGORY_MISMATCH,
AVAILABLE_QUANTITY_MISMATCH, NEED_ELECTRICITY_MISMATCH, PAYMENT_METHOD_MISMATCH)
into the LinkedHashSet so Swagger reflects possible validation/binding failures
for enum fields in the update request.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8f2e44 and 1431dcb.

📒 Files selected for processing (2)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java (2 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java
🔇 Additional comments (1)
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java (1)

12-13: MyFoodTruckMenuResponse에서 호출하는 menu.parsingMenuPrice() 메서드가 존재하지 않습니다. Menu.java에서 이 메서드를 찾을 수 없으며, 런타임에 NoMethodDefFoundError가 발생할 것입니다.

검증 결과:

  • Menu.javaprice 필드는 Integer 타입이고 nullable = false 제약조건을 가지고 있습니다.
  • FoodTruckMenuResponsemenu.getPrice()를 호출하며, 이는 Lombok의 @Getter로 자동 생성된 메서드입니다. int 타입으로의 변환은 null-safe합니다.
  • MyFoodTruckMenuResponsemenu.parsingMenuPrice()를 호출하지만, 이 메서드는 Menu.java에 존재하지 않습니다. 이는 리팩토링이 불완전함을 나타냅니다.

MyFoodTruckMenuResponse.java 라인 26에서 menu.parsingMenuPrice() 호출을 menu.getPrice()로 변경하거나, 해당 메서드를 Menu.java에 추가해야 합니다.

Likely an incorrect or invalid review comment.

menu.getMenuId(),
menu.getName(),
menu.parsingMenuPrice(),
menu.getPrice(),

Copy link
Copy Markdown

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

Menu.getPrice() 반환 타입을 검증하세요.

변경 사항은 Menu 엔티티 리팩토링과 일관성이 있어 보입니다. 다만, Menu.getPrice() 메서드가 실제로 int(또는 위 제안에 따라 Long)를 반환하는지, 그리고 null 안전성이 보장되는지 확인이 필요합니다.

다음 스크립트로 Menu 엔티티의 price 필드 타입과 getPrice() 메서드를 확인하세요:


🏁 Script executed:

#!/bin/bash
# Menu 엔티티에서 price 필드와 getPrice() 메서드 확인
rg -n -A5 -B5 'class Menu' --type java | head -50
rg -n 'getPrice\(\)' --type java -C3

Length of output: 4607


🏁 Script executed:

#!/bin/bash
# Search for price field definition in Menu entity
rg -n 'price' src/main/java/konkuk/chacall/domain/foodtruck/domain/model/Menu.java -C3

Length of output: 949


🏁 Script executed:

#!/bin/bash
# Check FoodTruckMenuResponse class definition
cat src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java

Length of output: 977


Menu.getPrice()가 Integer를 반환하지만 FoodTruckMenuResponse에서 primitive int로 변환됩니다.

Menu.price는 Integer 타입이고 @Column(nullable = false)는 데이터베이스 제약만 보장합니다. FoodTruckMenuResponse의 price 필드는 primitive int이므로, null 값이 있으면 NullPointerException이 발생합니다.

해결 방법 중 하나를 선택하세요:

  • FoodTruckMenuResponse.price를 Integer로 변경
  • Menu.price를 primitive int로 변경
  • Menu.price에 @NotNull 검증 추가
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/FoodTruckMenuResponse.java
around line 24, the code passes menu.getPrice() (an Integer) into a primitive
int field causing potential NPE if price is null; pick one fix and implement it:
(1) change FoodTruckMenuResponse.price from primitive int to Integer and update
constructor/getter usages, or (2) change Menu.price to primitive int and update
persistence/code to ensure non-null defaults, or (3) add @NotNull to Menu.price
and enforce validation before mapping; prefer option (1) for minimal
impact—update the DTO type to Integer and adjust any callers accordingly.

@ksg1227 ksg1227 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨습니다! 기존에 다 카톡으로 논의했던 내용들이라 코드를 보았을 때 딱히 건드릴 부분이 없는 것 같네요! 굿굿

한 가지 요청드릴 게 있는데, MyFoodTruckService -> updateFoodTruckViewedStatus() 메서드라고, 푸드트럭 노출 상태를 변경하는 API 가 있는데, 이 때도 canChangeViewedStatus 필드값이 true 일 때만 노출 상태를 변경할 수 있도록 수정해주어야할 것 같아요.

public void updateFoodTruckViewedStatus(Long ownerId, Long foodTruckId, UpdateFoodTruckViewedStatusRequest request) {

    // 본인 소유인지, 푸드트럭이 승인 완료된 상태인지 검증
    FoodTruck foodTruck = foodTruckRepository.findByFoodTruckIdAndOwnerIdAndFoodTruckStatus(foodTruckId, ownerId, FoodTruckStatus.APPROVED)
            .orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_APPROVED));

    if(!foodTruck.getCanChangeViewedStatus()) {
        throw new BuisnessException(ErrorCode.CANNOT_CHANGE_VIEWED_STATUS);
    }

    // 메뉴 표시 여부 변경 및 상태 전이 검증
    foodTruck.changeViewedStatus(request.status());
}

위와 같은 형태로 간단하게 구현이 가능할 것 같아서 혹시 괜찮으시다면 이 PR에서 작업을 추가해주실 수 있나용?

추가적으로

위에서 말씀드린 것처럼 현재 구현되어 있는 푸드트럭 메뉴 삭제 API에서 S3에 삭제까지 담당하고 있는데, 그대로 유지할지 혹은 이번에 구현한 API에 메뉴 이미지 url도 함께 포함하여 보내도록 할지 고민이 되었는데 이 부분에 대한 상균님 의견 궁금합니다~

이 논의사항에 대한 제 의견을 말씀드리자면, 메뉴 같은 경우는 푸드트럭 정보기입/수정 API 말고도 별도로 수정하는 것이 가능한 것으로 알고있어서, 메뉴 삭제 API 에서는 S3 이미지 삭제 로직을 함께 품고있는 것이 낫다고 생각하긴 합니다.
푸드트럭 이미지 삭제 로직에 메뉴 이미지까지 한 번에 넣어버리는 방식을 사용하면, 메뉴만을 단일로 삭제하는 로직에서 재활용하기 좀 애매해질 것 같아서요!

하지만 외부 네트워크를 타는 작업(S3 이미지 삭제)이 차콜 DB의 데이터를 삭제하는 로직과 하나의 트랜잭션으로 묶여버리는 것은 좀 좋지 않다는 생각이 들긴 해서, 사실상 제일 좋은 방식은

  1. 메뉴 이미지 삭제 API 를 새롭게 만든다. (푸드트럭 이미지 삭제와는 별도여야할 것 같습니다.)
  2. 푸드트럭 수정 API 에서 메뉴를 삭제할 경우, 프론트가 메뉴 삭제 API 로 요청 및 메뉴 이미지 삭제 API 로 추가 요청을 보내도록 한다.
  3. 나의 푸드트럭 메뉴 삭제 API 같은 경우도, 이 API 내에서는 차콜 DB 에서만 데이터를 삭제하도록 하고, 실제로 S3에서 이미지를 삭제하는 기능은 메뉴 이미지 삭제 API 로 따로 요청을 보내게, 즉 2개의 요청을 보냄으로써 처리하도록 한다.

이 방식이 가장 나을 것 같다고 생각합니다! 현준님의 의견은 어떠신가요?

foodTruck.updateFoodTruckInfo(request.name(), request.description(), request.phoneNumber(), request.activeTime(), request.timeDiscussRequired(), request.photoUrls(),
request.menuCategories(), request.availableQuantity(), request.needElectricity(), request.paymentMethod(), request.operatingInfo(), request.option());

// 푸드트럭 노출 상태 변경 허용

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

굿굿

Comment on lines +102 to +120
private void syncServiceAreas(FoodTruck foodTruck, Set<Long> requestedRegionIds) {
Set<Long> currentRegionIds = foodTruckServiceAreaRepository.findAllByFoodTruckId(foodTruck.getFoodTruckId()).stream()
.map(FoodTruckServiceArea::getRegion)
.map(Region::getRegionId)
.collect(Collectors.toSet());

// 추가할 지역
Set<Long> regionsToAdd = new HashSet<>(requestedRegionIds);
regionsToAdd.removeAll(currentRegionIds); // 현재 지역에서 없는 것들만 남김

// 제거할 지역
Set<Long> regionsToRemove = new HashSet<>(currentRegionIds);
regionsToRemove.removeAll(requestedRegionIds); // 요청된 지역에서 없는 것들만 남김

// 추가
if(!regionsToAdd.isEmpty()) {
List<FoodTruckServiceArea> serviceAreasToAdd = regionsToAdd.stream()
.map(regionId -> FoodTruckServiceArea.createFoodTruckServiceArea(regionRepository.findById(regionId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.REGION_NOT_FOUND)), foodTruck))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

어떤 데이터를 추가하고 어떤 데이터를 빼야할지 잘 고려된 것 같네요 좋습니다!

@Override
public Slice<FoodTruck> getFoodTrucks(FoodTruckSearchRequest request) {
BooleanBuilder where = new BooleanBuilder();
QFoodTruckInfo foodTruckInfo = foodTruck.foodTruckInfo;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

오호 VO 에 대해서도 QClass 를 잘 적용해주셨네요 좋습니다!

Comment on lines +26 to +34
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static AvailableQuantity from(String value) {
for (AvailableQuantity quantity : AvailableQuantity.values()) {
if (quantity.getValue().equals(value)) {
return quantity;
}
}
throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@JsonCreator 확인 했습니다~

@AllArgsConstructor
@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED)
@Embeddable
public class FoodTruckInfo {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

VO 로 따로 분리하니까 확실히 FoodTruck 클래스가 가벼워진 것 같네유

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

아 추가적으로 지금 푸드트럭 등록 뷰를 보면 200인분 미만 이라는 값이 새로 생긴 것 같더라구요. 그래서 이 ENUM 값도 하나만 추가해주시면 좋을 것 같습니다!!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

넵 추가하겠습니다~

Comment on lines +46 to +51
@Schema(description = "제조 가능 수량", example = "100인분 미만", allowableValues = {
"50인분 미만",
"100인분 미만",
"150인분 미만",
"200인분 이상",
"논의 필요"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

200인분 미만 이라는 값이 추가되면 여기 명세도 수정해야할 것 같아여

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java (1)

29-33: 선택적 제안: 성능 최적화를 고려할 수 있습니다.

현재 구현된 선형 탐색은 6개의 enum 값에 대해 충분히 빠르지만, 향후 enum 값이 증가할 경우를 대비하여 정적 Map을 사용한 O(1) 조회로 개선할 수 있습니다.

예시:

+    private static final Map<String, AvailableQuantity> VALUE_MAP = 
+            Arrays.stream(values())
+                  .collect(Collectors.toMap(AvailableQuantity::getValue, Function.identity()));
+
     @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
     public static AvailableQuantity from(String value) {
-        for (AvailableQuantity quantity : AvailableQuantity.values()) {
-            if (quantity.getValue().equals(value)) {
-                return quantity;
-            }
-        }
-        throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
+        AvailableQuantity quantity = VALUE_MAP.get(value);
+        if (quantity == null) {
+            throw new DomainRuleException(ErrorCode.AVAILABLE_QUANTITY_MISMATCH);
+        }
+        return quantity;
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b1fb5a and ade62c6.

📒 Files selected for processing (3)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/UpdateFoodTruckInfoRequest.java
🔇 Additional comments (2)
src/main/java/konkuk/chacall/domain/foodtruck/domain/value/AvailableQuantity.java (1)

17-17: 새로운 enum 상수와 역직렬화 로직이 올바르게 구현되었습니다.

LESS_THAN_200 상수 추가와 from() 메서드를 통한 JSON 역직렬화, 그리고 acceptableFor() 메서드 내 로직 업데이트가 모두 정확합니다. @JsonCreator(mode = DELEGATING)의 사용도 적절하며, 에러 처리도 일관성 있게 구현되었습니다.

Also applies to: 27-35, 43-49

src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/FoodTruckSearchRequest.java (1)

42-42: Swagger 문서 업데이트가 정확합니다.

Line 42에서 "200인분 미만" 추가는 AvailableQuantity enum 변경사항과 일치하며, Line 63에서 전기 사용 여부 용어를 "필요"/"불필요"로 변경한 것은 PR 목표와 일치합니다. 두 변경 모두 API 문서를 올바르게 반영하고 있습니다.

Also applies to: 63-63

@buzz0331

Copy link
Copy Markdown
Contributor Author

수고하셨습니다! 기존에 다 카톡으로 논의했던 내용들이라 코드를 보았을 때 딱히 건드릴 부분이 없는 것 같네요! 굿굿

한 가지 요청드릴 게 있는데, MyFoodTruckService -> updateFoodTruckViewedStatus() 메서드라고, 푸드트럭 노출 상태를 변경하는 API 가 있는데, 이 때도 canChangeViewedStatus 필드값이 true 일 때만 노출 상태를 변경할 수 있도록 수정해주어야할 것 같아요.

public void updateFoodTruckViewedStatus(Long ownerId, Long foodTruckId, UpdateFoodTruckViewedStatusRequest request) {

    // 본인 소유인지, 푸드트럭이 승인 완료된 상태인지 검증
    FoodTruck foodTruck = foodTruckRepository.findByFoodTruckIdAndOwnerIdAndFoodTruckStatus(foodTruckId, ownerId, FoodTruckStatus.APPROVED)
            .orElseThrow(() -> new BusinessException(ErrorCode.FOOD_TRUCK_NOT_APPROVED));

    if(!foodTruck.getCanChangeViewedStatus()) {
        throw new BuisnessException(ErrorCode.CANNOT_CHANGE_VIEWED_STATUS);
    }

    // 메뉴 표시 여부 변경 및 상태 전이 검증
    foodTruck.changeViewedStatus(request.status());
}

위와 같은 형태로 간단하게 구현이 가능할 것 같아서 혹시 괜찮으시다면 이 PR에서 작업을 추가해주실 수 있나용?

추가적으로

위에서 말씀드린 것처럼 현재 구현되어 있는 푸드트럭 메뉴 삭제 API에서 S3에 삭제까지 담당하고 있는데, 그대로 유지할지 혹은 이번에 구현한 API에 메뉴 이미지 url도 함께 포함하여 보내도록 할지 고민이 되었는데 이 부분에 대한 상균님 의견 궁금합니다~

이 논의사항에 대한 제 의견을 말씀드리자면, 메뉴 같은 경우는 푸드트럭 정보기입/수정 API 말고도 별도로 수정하는 것이 가능한 것으로 알고있어서, 메뉴 삭제 API 에서는 S3 이미지 삭제 로직을 함께 품고있는 것이 낫다고 생각하긴 합니다. 푸드트럭 이미지 삭제 로직에 메뉴 이미지까지 한 번에 넣어버리는 방식을 사용하면, 메뉴만을 단일로 삭제하는 로직에서 재활용하기 좀 애매해질 것 같아서요!

하지만 외부 네트워크를 타는 작업(S3 이미지 삭제)이 차콜 DB의 데이터를 삭제하는 로직과 하나의 트랜잭션으로 묶여버리는 것은 좀 좋지 않다는 생각이 들긴 해서, 사실상 제일 좋은 방식은

  1. 메뉴 이미지 삭제 API 를 새롭게 만든다. (푸드트럭 이미지 삭제와는 별도여야할 것 같습니다.)
  2. 푸드트럭 수정 API 에서 메뉴를 삭제할 경우, 프론트가 메뉴 삭제 API 로 요청 및 메뉴 이미지 삭제 API 로 추가 요청을 보내도록 한다.
  3. 나의 푸드트럭 메뉴 삭제 API 같은 경우도, 이 API 내에서는 차콜 DB 에서만 데이터를 삭제하도록 하고, 실제로 S3에서 이미지를 삭제하는 기능은 메뉴 이미지 삭제 API 로 따로 요청을 보내게, 즉 2개의 요청을 보냄으로써 처리하도록 한다.

이 방식이 가장 나을 것 같다고 생각합니다! 현준님의 의견은 어떠신가요?

엇 푸드트럭 노출 상태 변경 API도 같이 고친다는걸 누락했던 것 같네요,, 바로 수정하겠습니다!
S3 이미지 삭제에 관해서는 저도 상균님 의견에 동의합니다. 한 트랜잭션에 두 영속성 리소스(S3, RDS)를 함께 접근하는 것은 좋지 않다고 생각해서 분리하는게 좋다고 생각합니다.
다만, 현재 S3에서 이미지 객체 삭제라는 API를 이미 만들어놔서 프론트 입장에서 이 API를 이용하면 따로 도메인 구분없이 메뉴 이미지까지 삭제할 수 있지 않을까? 싶긴합니다. 현재 presignedURL 발급 API는 각 도메인마다 분리되어 있는 이유가 S3에 저장시에 key를 디렉토리화 시키기 위함인데 이미지 삭제 API는 따로 이런 이슈가 없기 때문에 통합해도 괜찮지 않을까? 싶긴 합니다! 괜찮으신가요..?

@buzz0331

Copy link
Copy Markdown
Contributor Author

@ksg1227 생각해보니까 제가 FoodTruck 도메인 내부에서 다음과 같이 유효성 검증을 추가해뒀었네요! 누락한줄 알았는데 도메인 규칙으로 처리했었던 것 같습니다.

public void changeViewedStatus(FoodTruckViewedStatus targetViewedStatus) {
        if(this.foodTruckViewedStatus == targetViewedStatus) {
            throw new DomainRuleException(ErrorCode.INVALID_FOOD_TRUCK_STATUS_TRANSITION);
        }

        if(!this.canChangeViewedStatus) {
            throw new DomainRuleException(ErrorCode.FOOD_TRUCK_VIEWED_STATUS_CHANGE_NOT_PERMITTED,
                    new IllegalArgumentException("최소 1번의 정보 기입 후에만 푸드트럭 노출 상태를 변경할 수 있습니다."));
        }

        this.foodTruckViewedStatus = targetViewedStatus;
    }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ade62c6 and d18efd3.

📒 Files selected for processing (3)
  • src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (3 hunks)
🔇 Additional comments (2)
src/main/java/konkuk/chacall/domain/member/application/reservation/MemberReservationService.java (1)

45-45: 리뷰 필요 여부 판단 로직 검증 필요

RatingRepository.existsByMemberAndReservation 메서드가 isRated 상태를 확인하지 않아, 이미 작성된 리뷰에 대해서도 reviewRequired=true가 설정될 수 있습니다. RatingRepository의 메서드를 existsByMemberAndReservationAndIsRatedFalse로 수정한 후, 여기서도 해당 메서드를 호출하도록 변경해주세요.

src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java (1)

43-44: 리뷰 필요 여부 필드 추가가 적절합니다

reviewRequired 필드가 올바르게 추가되었고, 팩토리 메서드 시그니처 및 응답 생성 로직이 일관되게 업데이트되었습니다. API 스키마 설명도 명확합니다.

Also applies to: 47-47, 62-63

@Query("DELETE FROM Rating r WHERE r.foodTruck.foodTruckId = :foodTruckId")
void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

boolean existsByMemberAndReservation(User member, Reservation reservation);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

리뷰 작성 여부(isRated)를 확인하지 않는 잠재적 로직 오류

기존 Line 18의 메서드를 보면 isRatedFalse 조건을 사용하는 것을 확인할 수 있습니다. 현재 메서드는 Rating 엔티티의 존재 여부만 확인하고 isRated 필드를 검증하지 않습니다.

만약 이미 리뷰가 작성된 Rating(isRated=true)이 존재하는 경우에도 이 메서드는 true를 반환하게 되어, MemberReservationService에서 reviewRequired가 잘못 설정될 수 있습니다.

다음과 같이 수정하는 것을 권장합니다:

-    boolean existsByMemberAndReservation(User member, Reservation reservation);
+    boolean existsByMemberAndReservationAndIsRatedFalse(User member, Reservation reservation);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
boolean existsByMemberAndReservation(User member, Reservation reservation);
boolean existsByMemberAndReservationAndIsRatedFalse(User member, Reservation reservation);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java
around line 30, the repository method only checks existence of a Rating by
member and reservation but does not verify the isRated flag, which can return
true even for already-reviewed ratings; change the method signature to check
isRated == false (e.g., existsByMemberAndReservationAndIsRatedFalse) so callers
get correct "not yet reviewed" semantics, and update any call sites
(MemberReservationService and tests) to use the new method name.

public static MemberReservationDetailResponse of(Reservation reservation, FoodTruck foodTruck) {
public static MemberReservationDetailResponse of(Reservation reservation, FoodTruck foodTruck, boolean reviewRequired) {
List<String> dateTimeList = reservation.getReservationInfo().getFormattedDateTimeInfos();
FoodTruckInfo foodTruckInfo = foodTruck.getFoodTruckInfo();

Copy link
Copy Markdown

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

FoodTruckInfo 마이그레이션이 적절하며, null 안전성 확인 필요

FoodTruckInfo를 통해 푸드트럭 정보에 접근하는 것은 PR 목표와 일치하며 코드 구조가 개선되었습니다.

다만, Line 52의 foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl() 호출에서 getFoodTruckPhotoList()null을 반환하거나 메인 사진이 없는 경우 NullPointerException이 발생할 수 있습니다. FoodTruckInfoFoodTruckPhotoList 클래스에서 이러한 경우를 안전하게 처리하는지 확인해주세요.

다음 스크립트를 실행하여 null 안전성을 확인할 수 있습니다:

Also applies to: 52-53


🏁 Script executed:

#!/bin/bash
# FoodTruckInfo 및 FoodTruckPhotoList의 null 안전성 확인

# FoodTruckPhotoList 클래스의 구현 확인
ast-grep --pattern $'class FoodTruckPhotoList {
  $$$
  getMainPhotoUrl($$$) {
    $$$
  }
  $$$
}'

# FoodTruckInfo의 getFoodTruckPhotoList 메서드 확인
ast-grep --pattern $'class FoodTruckInfo {
  $$$
  getFoodTruckPhotoList($$$) {
    $$$
  }
  $$$
}'

Length of output: 0


정보 수집을 위해 추가 스크립트를 실행하겠습니다.

</script>

🏁 Script executed:

#!/bin/bash
# FoodTruckInfo 클래스 찾기
echo "=== Searching for FoodTruckInfo class ==="
rg -n "class FoodTruckInfo" --type java

echo ""
echo "=== Searching for FoodTruckPhotoList class ==="
rg -n "class FoodTruckPhotoList" --type java

echo ""
echo "=== Viewing MemberReservationDetailResponse.java around lines 49-53 ==="
rg -n -A 10 -B 2 "getFoodTruckInfo" src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java

Length of output: 1280


</script>

🏁 Script executed:

#!/bin/bash
# FoodTruckInfo 클래스 전체 구현 확인
echo "=== FoodTruckInfo.java content ==="
cat src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java

echo ""
echo "=== Search for getFoodTruckPhotoList method return type ==="
rg -n -A 3 "getFoodTruckPhotoList" src/main/java/konkuk/chacall/domain/foodtruck/domain/value/FoodTruckInfo.java

echo ""
echo "=== Check FoodTruck class for getFoodTruckInfo method ==="
rg -n -A 3 "getFoodTruckInfo" --type java | head -20

Length of output: 5081


</script>

🏁 Script executed:

#!/bin/bash
# PhotoUrlList 클래스 찾기 및 구현 확인
echo "=== Searching for PhotoUrlList class ==="
rg -n "class PhotoUrlList" --type java

echo ""
echo "=== PhotoUrlList implementation ==="
rg -n -A 20 "class PhotoUrlList" --type java

echo ""
echo "=== Check PhotoUrlListConverter for null handling ==="
rg -n -A 15 "class PhotoUrlListConverter" --type java

echo ""
echo "=== Check if foodTruckPhotoList is ever validated or initialized ==="
rg -n "foodTruckPhotoList" --type java | grep -E "(null|initialize|require|valid)" | head -10

Length of output: 7057


getFoodTruckPhotoList().getMainPhotoUrl() 체이닝 호출에 null-safety 추가 필요

코드 검증 결과, Line 52의 잠재적 NPE 위험이 확인되었습니다.

  • FoodTruckInfo.foodTruckPhotoList 필드는 초기화되지 않으며 @nullable이 아님
  • PhotoUrlListConverter는 DB에서 로드할 때 항상 PhotoUrlList 객체를 반환하지만, createEmptyFoodTruckInfo() 등으로 생성 시 필드가 null일 수 있음
  • PhotoUrlList.getMainPhotoUrl()는 빈 리스트일 때 null을 반환하지만 예외는 발생하지 않음

필수 수정: Line 52에서 foodTruckInfo.getFoodTruckPhotoList()가 null이 아님을 보장하거나, null-safe 접근 방식(Optional, 삼항연산자 등)을 적용하세요.

// 방안 1: null 체크
String mainPhotoUrl = foodTruckInfo.getFoodTruckPhotoList() != null 
    ? foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl() 
    : null;

// 방안 2: Optional 활용
String mainPhotoUrl = Optional.ofNullable(foodTruckInfo.getFoodTruckPhotoList())
    .map(PhotoUrlList::getMainPhotoUrl)
    .orElse(null);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/member/presentation/dto/response/MemberReservationDetailResponse.java
around line 49, calling foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl()
can NPE if getFoodTruckPhotoList() is null; update the code to guard that call
by checking for null (ternary/null-check) or using
Optional.ofNullable(...).map(...).orElse(null) so mainPhotoUrl is safely
assigned null when the photo list is missing.

@ksg1227

ksg1227 commented Oct 25, 2025

Copy link
Copy Markdown
Contributor

엇 푸드트럭 노출 상태 변경 API도 같이 고친다는걸 누락했던 것 같네요,, 바로 수정하겠습니다!
S3 이미지 삭제에 관해서는 저도 상균님 의견에 동의합니다. 한 트랜잭션에 두 영속성 리소스(S3, RDS)를 함께 접근하는 것은 좋지 않다고 생각해서 분리하는게 좋다고 생각합니다.
다만, 현재 S3에서 이미지 객체 삭제라는 API를 이미 만들어놔서 프론트 입장에서 이 API를 이용하면 따로 도메인 구분없이 메뉴 이미지까지 삭제할 수 있지 않을까? 싶긴합니다. 현재 presignedURL 발급 API는 각 도메인마다 분리되어 있는 이유가 S3에 저장시에 key를 디렉토리화 시키기 위함인데 이미지 삭제 API는 따로 이런 이슈가 없기 때문에 통합해도 괜찮지 않을까? 싶긴 합니다! 괜찮으신가요..?

메뉴는 나의 푸드트럭 수정 뷰 말고는 수정할 곳이 없다는 점. 그리고 이미지 객체 삭제 API 가 따로 있다는 점에서 괜찮은 것 같습니다!! 통합해도 좋을 것 같아요~~

그리고 도메인 규칙으로 처리하신 부분도 확인했습니당

ksg1227
ksg1227 previously approved these changes Oct 25, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java (2)

18-19: 사용하지 않는 의존성을 제거하세요.

S3 삭제 로직이 제거되면서 S3ServiceCdnUrlResolver가 더 이상 사용되지 않습니다.

다음 diff를 적용하여 사용하지 않는 import와 필드를 제거하세요:

 import konkuk.chacall.global.common.exception.BusinessException;
 import konkuk.chacall.global.common.exception.code.ErrorCode;
-import konkuk.chacall.global.common.storage.S3Service;
-import konkuk.chacall.global.common.storage.util.CdnUrlResolver;
 import lombok.RequiredArgsConstructor;
 @RequiredArgsConstructor
 @Service
 public class MyFoodTruckMenuService {
 
     private final FoodTruckRepository foodTruckRepository;
     private final MenuRepository menuRepository;
-
-    private final CdnUrlResolver cdnUrlResolver;
-    private final S3Service s3Service;

Also applies to: 35-36


106-125: 메뉴 삭제 시 S3 이미지 고아 객체 발생 - 해결되지 않음

코드베이스 검증 결과, 원래 제기된 우려사항이 확인되었습니다:

  1. 메뉴 이미지 삭제 API 없음: 푸드트럭(deleteFoodTruckImagesFromS3)과 달리, 메뉴 삭제를 위한 별도의 이미지 삭제 엔드포인트가 존재하지 않습니다.

  2. S3 삭제 로직이 주석 처리된 상태: MyFoodTruckMenuService.deleteMenu()의 S3 삭제 코드(라인 113-118)가 주석 처리되어 있으며, DB 삭제만 수행됩니다(라인 124).

  3. 고아 객체 누적 위험: 사용자가 메뉴를 삭제할 때, 연결된 S3 이미지는 삭제되지 않아 불필요한 스토리지 비용이 계속 발생합니다.

필요한 조치:

  • 메뉴 이미지 삭제 API를 명시적으로 구현하거나
  • deleteMenu() 메서드 내에서 S3 삭제 로직을 활성화하고 트랜잭션 오류 처리를 추가하거나
  • S3에 대한 주기적인 정리 작업(고아 객체 감지 및 삭제)을 구현해야 합니다.
🧹 Nitpick comments (3)
src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java (3)

8-9: 와일드카드 import 대신 명시적 import를 권장합니다.

와일드카드 import는 실제로 사용되는 클래스를 명확히 파악하기 어렵게 만들고, 네이밍 충돌 가능성을 높입니다. 명시적으로 필요한 클래스만 import하는 것이 Java 컨벤션에 더 부합합니다.


101-101: 경로 매핑에 선행 슬래시를 추가하는 것이 좋습니다.

@PutMapping("{foodTruckId}") 대신 @PutMapping("/{foodTruckId}")를 사용하는 것이 Spring의 일반적인 컨벤션입니다. 기능적으로는 동일하지만 명시적인 표현이 가독성을 높입니다.


115-115: 경로 매핑에 선행 슬래시를 추가하는 것이 좋습니다.

@DeleteMapping("{foodTruckId}/images") 대신 @DeleteMapping("/{foodTruckId}/images")를 사용하는 것이 Spring의 일반적인 컨벤션입니다.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d18efd3 and 88348db.

📒 Files selected for processing (2)
  • src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java (1 hunks)


@Operation(
summary = "S3에서 푸드트럭 이미지 객체 삭제",
description = "S3에서 푸드트럭/메뉴 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭/메뉴 이미지를 삭제했을 경우 호출해주세요."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

API 설명이 구현 범위와 불일치합니다.

현재 설명에는 "푸드트럭/메뉴 이미지 객체를 삭제합니다"라고 되어 있지만, PR 목표와 코멘트에 따르면 이 API는 푸드트럭 이미지만 처리하고 메뉴 이미지는 기존 메뉴 삭제 API에서 담당하도록 설계되었습니다. 설명에서 "메뉴 이미지" 부분을 제거하여 API 소비자의 혼란을 방지해야 합니다.

다음과 같이 수정하는 것을 권장합니다:

-            description = "S3에서 푸드트럭/메뉴 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭/메뉴 이미지를 삭제했을 경우 호출해주세요."
+            description = "S3에서 푸드트럭 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭 이미지를 삭제했을 경우 호출해주세요."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
description = "S3에서 푸드트럭/메뉴 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭/메뉴 이미지를 삭제했을 경우 호출해주세요."
description = "S3에서 푸드트럭 이미지 객체를 삭제합니다. 사용자가 기존 푸드트럭 이미지를 삭제했을 경우 호출해주세요."
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java
around line 112, the Swagger/API description incorrectly mentions both "푸드트럭/메뉴
이미지" whereas this endpoint only deletes food truck images; update the
description string to remove the "메뉴 이미지" part so it reads that it deletes S3
food truck image objects and advise callers to use the menu-delete API for menu
images, ensuring the new text remains concise and in Korean matching existing
style.

Comment on lines +117 to +122
// String key = cdnUrlResolver.extractKeyFromUrl(menu.getImageUrl());

// 메뉴 이미지를 S3 에서 삭제
if (key != null) {
s3Service.delete(key);
}
// // 메뉴 이미지를 S3 에서 삭제
// if (key != null) {
// s3Service.delete(key);
// }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

주석 처리된 코드를 제거하세요.

버전 관리(git)를 통해 이전 코드를 추적할 수 있으므로, 주석 처리된 코드는 삭제하는 것이 좋습니다.

다음 diff를 적용하여 주석 처리된 코드를 제거하세요:

-//        String key = cdnUrlResolver.extractKeyFromUrl(menu.getImageUrl());
-
-//        // 메뉴 이미지를 S3 에서 삭제
-//        if (key != null) {
-//            s3Service.delete(key);
-//        }
-
         menuRepository.delete(menu);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// String key = cdnUrlResolver.extractKeyFromUrl(menu.getImageUrl());
// 메뉴 이미지를 S3 에서 삭제
if (key != null) {
s3Service.delete(key);
}
// // 메뉴 이미지를 S3 에서 삭제
// if (key != null) {
// s3Service.delete(key);
// }
menuRepository.delete(menu);
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/owner/application/myfoodtruckmenu/MyFoodTruckMenuService.java
around lines 117 to 122, remove the commented-out block that extracts the S3 key
and deletes the menu image (the three commented lines shown) so there is no
dead/commented code left; keep behavior unchanged otherwise and commit the
deletion.

@buzz0331 buzz0331 merged commit d8ab9f1 into develop Oct 28, 2025
2 checks passed
@buzz0331 buzz0331 deleted the feat/#49-foodtruck-put-info branch October 28, 2025 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 푸드트럭 정보기입/수정 API 구현

2 participants