Skip to content

[FEAT] 사장님 - 나의 푸드트럭 조회/삭제 API 구현#21

Merged
ksg1227 merged 16 commits into
developfrom
feat/#16-owner-foodtruck-read-delete
Sep 19, 2025
Merged

[FEAT] 사장님 - 나의 푸드트럭 조회/삭제 API 구현#21
ksg1227 merged 16 commits into
developfrom
feat/#16-owner-foodtruck-read-delete

Conversation

@ksg1227

@ksg1227 ksg1227 commented Sep 17, 2025

Copy link
Copy Markdown
Contributor

#️⃣연관된 이슈

closes #16

📝작업 내용

나의 푸드트럭 조회/삭제 API 를 구현하였습니다.

나의 푸드트럭 조회

나의 푸드트럭 조회 기능에는 페이징 로직이 존재하지만 페이징을 위한 파라미터 이외의 별도의 쿼리 파라미터를 받아주어야할 필요성이 없었기에 CursorPagingRequest 만을 전달받아 사용하도록 하였습니다.

이 기능을 구현하면서 가장 중점적으로 구현했던 부분은 OneToMany 관계에서 fetch join 을 사용하지 않으면서 최대한 효율적으로 페이징 로직을 구현하는 것이었습니다.

나의 푸드트럭 조회 기능에서는 각각의 푸드트럭을 조회하면서 각 푸드트럭의 호출 가능 지역 을 보여줄 필요가 있습니다.
이 때 FoodTruck : FoodTruckServiceArea(푸드트럭 호출 가능 지역) 테이블은 서로 OneToMany 관계를 갖고있는데, 만약 나의 푸드트럭을 조회하면서 별도의 쿼리를 날리지 않고자 fetch join 을 통해서 호출 가능 지역 까지 조회해오고자 할 경우, 페이징 로직이 문제를 일으켜서 성능이 극단적으로 안좋아질 위험이 있었습니다.

따라서 저는 우선적으로 로그인한 사장님 소유의 모든 푸드트럭을 조회하도록 하였습니다.
그 후 조회된 푸드트럭들의 ID 를 모두 추출한 후, 해당 ID 값을 기반으로 FoodTruckServiceArea 테이블을 조회하도록 하였습니다.
이 때 실제로 화면에 보여주어야하는 지역 정보 (ex) 서울 전체, 인천 계양구) 등은 Region 테이블에 존재하는 record 들의 fullname 값을 조회해와서 보여주어야했기에 이 시점에 FoodTruckServiceArea 테이블을 조회하면서 동시에 Region 테이블의 데이터까지 조회해올 수 있도록 fetch join 을 여기에 걸어주었습니다.
이 때는 이전과 달리 fetch join 을 적용해도 문제가 발생하지 않는 이유는, FoodTruckServiceArea 테이블과 Region 테이블은 서로 ManyToOne 관계를 갖고 있기 때문입니다. 페이징 로직 + fetch Join 조합이 문제를 일으키는 경우는 ~~ToMany 관계이기 때문에, 현재 관계에서는 fetch join 을 적용해도 문제가 없었습니다.

즉 요약하자면, 푸드트럭을 조회하면서 FoodTruckServiceArea, Region 테이블을 한 번에 조회할 경우 페이징 로직이 극심한 성능 저하를 보일 수 있기 때문에 해당 문제를 방지하고자
푸드트럭 조회 쿼리 1회 + FoodTruckServiceArea + Region 을 fetch join 으로 한 번에 조회하는 쿼리 1회 => 총 2회의 쿼리로 요구사항을 만족할 수 있도록 기능을 구현하였습니다.

나의 푸드트럭 삭제

삭제 로직은 크게 이슈가 될 부분은 없었습니다.
다만 푸드트럭을 지웠을 때, 그에 따라 추가적으로 지워주어야하는 데이터들이 무엇인지 파악하는 것이 중요했습니다.
제가 판단하기로는 푸드트럭 하나를 삭제할 시, 추가적으로 삭제되어야하는 데이터들은 다음과 같았습니다.

  1. 푸드트럭 호출 가능 지역 (FoodTruckServiceArea)
  2. 푸드트럭 메뉴 (Menu)
  3. 푸드트럭 가능한 일정대 (AvailableDate)
  4. 푸드트럭 관련 평점 (Rating)
  5. 푸드트럭 관련 예약 (Reservation)
  6. 저장한 푸드트럭 (SavedFoodTruck)

이 데이터들을 지워줄 필요가 있었습니다.
그래서 푸드트럭을 삭제하기 이전에 위의 데이터들을 선행해서 지워줄 수 있도록 하였습니다.
또한 여기서 한 가지 추가로 주의했던 점은, 예약 데이터 와 평점 데이터의 삭제 순서 였습니다.

현재 평점 테이블은 예약 테이블과 ManyToOne 관계를 갖고있고, 그에 따라 예약 테이블의 PK 값이 평점 테이블의 FK 로 걸려있는 상황입니다.
그러다보니 만약 평점 테이블을 삭제하기 이전에 예약 테이블을 먼저 삭제해버릴 경우, 평점 테이블에 걸려있는 FK(예약 테이블 PK) 가 null 값을 가리키게 되어서 외래키 제약 조건에 걸려 에러가 발생할 여지가 있다고 판단했습니다.

다만 보통 실제 쿼리 발생은 트랜잭션이 commit 되는 시점에 전송되기 때문에, 한 트랜잭션 내에서 여러 삭제 로직이 들어갈 때 실제로 문제가 발생하는지는 검증해보지 못했지만, 우선 해당 부분이 우려되었기에 평점 데이터 먼저 삭제 -> 그 이후에 예약 데이터 삭제 흐름으로 구성하여 외래키 제약 조건 문제가 발생하지 않도록 처리하였습니다.

스크린샷 (선택)

스크린샷 2025-09-17 오후 9 12 21

💬리뷰 요구사항(선택)

작업 내용에 페이징 관련 생각해볼 내용, 삭제 시에 삭제 순서를 좀 고민해야할 상황 등에 대해 정리해두었으니 한 번 확인 부탁드립니다~

Summary by CodeRabbit

  • New Features
    • 내 푸드트럭 목록 조회(커서 기반 무한 스크롤) 및 단건 삭제 API 추가
  • Refactor
    • 예약 상세/히스토리 응답 필드명 변경: userProfileImage → profileImage, username → name
    • 예약 히스토리 응답에 foodTruckName 추가
    • 소유권 검증 고도화 및 새로운 오류 코드(FOOD_TRUCK_NOT_OWNED) 도입
  • Documentation
    • 신규 오너 API에 대한 스웨거 응답 설명 추가 (조회/삭제)

@ksg1227 ksg1227 requested a review from buzz0331 September 17, 2025 12:13
@ksg1227 ksg1227 self-assigned this Sep 17, 2025
@ksg1227 ksg1227 linked an issue Sep 17, 2025 that may be closed by this pull request
2 tasks
@coderabbitai

coderabbitai Bot commented Sep 17, 2025

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

Walkthrough

사장님의 “나의 푸드트럭” 조회/삭제 기능을 추가했다. 이를 위해 컨트롤러·서비스·DTO를 신설/확장하고, 소유주 검증 로직과 예약 소유 판별 메서드를 개편했다. 연쇄 삭제를 위한 다수 JPA Repository에 일괄 삭제 쿼리를 추가하고, 커서 기반 조회를 위한 쿼리를 도입했다.

Changes

Cohort / File(s) Summary
Owner API: MyFoodTruck 조회/삭제 추가
src/main/java/.../owner/presentation/OwnerController.java, src/main/java/.../owner/application/OwnerService.java, src/main/java/.../owner/application/myfoodtruck/MyFoodTruckService.java, src/main/java/.../owner/presentation/dto/response/MyFoodTruckResponse.java, src/main/java/.../global/common/swagger/SwaggerResponseDescription.java
GET /owners/me/food-trucks 및 DELETE /owners/me/food-trucks/{id} 엔드포인트 추가. OwnerService에 MyFoodTruckService 연동 및 트랜잭션 어노테이션을 메서드 단위로 적용. MyFoodTruckService 신설: 커서 기반 조회와 연관 데이터 일괄 삭제 구현. Swagger 응답 설명(enum 항목) 추가. DTO MyFoodTruckResponse 신설.
연쇄 삭제/조회 지원 Repository 추가/확장
.../foodtruck/domain/repository/FoodTruckRepository.java, .../FoodTruckServiceAreaRepository.java, .../MenuRepository.java, .../AvailableDateRepository.java, .../reservation/domain/repository/ReservationRepository.java, .../member/domain/repository/SavedFoodTruckRepository.java, .../member/domain/repository/RatingRepository.java
FoodTruckRepository에 소유주 기준 커서 조회 메서드 추가. 서비스 지역/메뉴/예약/저장/평가/가용일 삭제용 JPQL @Modifying 메서드 추가. 서비스 지역 조회 시 region fetch join 제공. ReservationRepository는 두 조회 메서드 제거, 삭제 메서드 추가.
도메인: 소유 판별/서비스 지역 헬퍼
.../foodtruck/domain/FoodTruck.java, .../reservation/domain/model/Reservation.java, .../region/domain/Region.java
FoodTruck에 isOwnedBy(ownerId), getServiceAreas(list) 추가. Reservation의 isOwnedBy → isForFoodTruckOwnedBy로 변경하고 FoodTruck.isOwnedBy에 위임. Region에 @Getter 추가.
오너 예약 응답 DTO 필드 정비
.../owner/presentation/dto/response/OwnerReservationDetailResponse.java, .../OwnerReservationHistoryResponse.java
userProfileImage→profileImage, username→name으로 필드명 변경. History 응답에 foodTruckName 필드 추가 및 생성자/팩토리 매핑 갱신.
오너 예약 서비스 판별 메서드 변경
.../owner/application/reservation/OwnerReservationService.java
소유 확인 호출을 isForFoodTruckOwnedBy로 교체. 클래스 레벨 @transactional 제거.
에러 코드 추가
.../global/common/exception/code/ErrorCode.java
FOOD_TRUCK_NOT_OWNED(403, 110002) 추가.
패키지/트랜잭션 정리
.../owner/application/bankaccount/BankAccountService.java, .../owner/application/chattemplate/ChatTemplateService.java
패키지 경로 소문자 정규화(bankaccount, chattemplate). 클래스/메서드 수준 트랜잭션 어노테이션 제거 및 불필요한 import 정리.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Owner as Owner
  participant API as OwnerController
  participant Svc as OwnerService
  participant MFS as MyFoodTruckService
  participant FRepo as FoodTruckRepository
  participant SAR as FoodTruckServiceAreaRepository

  Owner->>API: GET /owners/me/food-trucks?lastCursor&pageSize
  API->>Svc: getMyFoodTrucks(request, ownerId)
  Svc->>MFS: getMyFoodTrucks(request, ownerId)
  rect rgb(235,245,255)
    MFS->>FRepo: findByOwnerUserIdWithCursor(ownerId, lastCursor, pageable)
    FRepo-->>MFS: Slice<FoodTruck>
    MFS->>SAR: findAllWithRegionByFoodTruckIdIn(ids)
    SAR-->>MFS: List<FoodTruckServiceArea>
    MFS-->>Svc: CursorPagingResponse<MyFoodTruckResponse>
  end
  Svc-->>API: BaseResponse<CursorPagingResponse>
  API-->>Owner: 200 OK
Loading
sequenceDiagram
  autonumber
  actor Owner as Owner
  participant API as OwnerController
  participant Svc as OwnerService
  participant MFS as MyFoodTruckService
  participant FRepo as FoodTruckRepository
  participant Del as Repositories
  note over Del: ServiceArea, Menu, AvailableDate,<br/>Rating, Reservation, SavedFoodTruck

  Owner->>API: DELETE /owners/me/food-trucks/{foodTruckId}
  API->>Svc: deleteMyFoodTruck(ownerId, foodTruckId)
  Svc->>MFS: deleteMyFoodTruck(ownerId, foodTruckId)
  rect rgb(245,235,245)
    MFS->>FRepo: findById(foodTruckId)
    alt 존재하지 않음
      MFS-->>Svc: throw FOOD_TRUCK_NOT_FOUND
    else 존재함
      MFS->>FRepo: entity.getOwner check via foodTruck.isOwnedBy(ownerId)
      alt 소유자 아님
        MFS-->>Svc: throw FOOD_TRUCK_NOT_OWNED
      else 소유자 맞음
        MFS->>Del: deleteAllByFoodTruckId(foodTruckId) 각각 호출
        MFS->>FRepo: delete(foodTruck)
        MFS-->>Svc: void
      end
    end
  end
  Svc-->>API: BaseResponse<Void>
  API-->>Owner: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • buzz0331

Poem

꼬박꼬박 커서를 따라 점프 점프-
내 트럭 목록, 한 입 크기씩 꿀-짝!
주인 아닌 이는 멈춰! 403 문패 달고,
삭제는 싹—연결 고리까지 휙.
오늘도 깃발 휘날리며 배포 완료! 🐇🚚✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning 본 PR 목표와 무관하게 BankAccountService와 ChatTemplateService의 트랜잭션 어노테이션 제거, OwnerReservationService의 트랜잭션 경계 변경 및 예약 관련 DTO(OwnerReservationDetailResponse, OwnerReservationHistoryResponse) 필드 수정 등 사장님 푸드트럭 조회/삭제 기능과 직접적 관련이 없는 여러 변경이 포함되어 있습니다. 사장님 푸드트럭 조회/삭제 API 구현과 관계없는 변경사항은 별도의 PR로 분리하거나 해당 커밋에서 제외해 주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.76% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed Title “[FEAT] 사장님 - 나의 푸드트럭 조회/삭제 API 구현”은 PR의 핵심 기능인 사장님 대상 푸드트럭 조회 및 삭제 API 도입을 명확하게 요약하고 있어 변경사항을 한눈에 파악할 수 있습니다.
Linked Issues Check ✅ Passed 링크된 이슈 #16의 “나의 푸드트럭 조회” 및 “나의 푸드트럭 삭제” 작업이 모두 구현되어 있으며, 페이징 조회와 연관 엔티티 삭제 로직이 정상적으로 반영되었습니다.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d404b6 and a307f03.

📒 Files selected for processing (4)
  • src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (11 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/bankaccount/BankAccountService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/chattemplate/ChatTemplateService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myfoodtruck/MyFoodTruckService.java (1 hunks)

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: 0

🧹 Nitpick comments (16)
src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java (1)

35-37: 벌크 삭제 시 1차 캐시 무효화 및 영향 범위 확인 권장

JPQL 벌크 연산은 영속성 컨텍스트를 우회합니다. 호출 전후로 Reservation 엔티티가 1차 캐시에 올라와 있으면 불일치가 날 수 있으니 clearAutomatically 옵션과 영향 행 수 반환을 권장합니다.

적용 예시:

-    @Modifying
-    @Query("DELETE FROM Reservation r WHERE r.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
+    @Query("DELETE FROM Reservation r WHERE r.foodTruck.foodTruckId = :foodTruckId")
+    int deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

또한 이 메서드는 반드시 @transactional 문맥에서, 관련 Rating 등 FK 제약 개체를 선삭제한 뒤 호출되는지 확인해 주세요.

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

13-16: IN 절 빈 리스트 처리 가드 필요

foodTruckIds가 빈 리스트일 경우 JPA가 파라미터 바인딩 예외를 던질 수 있습니다. 서비스 레벨에서 early return 하거나 쿼리를 조건부로 분기하세요.

적용 예시(서비스 레벨):

-List<FoodTruckServiceArea> areas = repo.findAllWithRegionByFoodTruckIdIn(ids);
+if (ids.isEmpty()) return List.of();
+List<FoodTruckServiceArea> areas = repo.findAllWithRegionByFoodTruckIdIn(ids);

18-20: 벌크 삭제 시 clear/flush 옵션 및 영향 행 수 반환 권장

영속성 컨텍스트 불일치 방지 및 로깅/검증을 위해 아래와 같이 변경을 권장합니다.

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM FoodTruckServiceArea ftsa WHERE ftsa.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    int deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (1)

73-75: 소유권 헬퍼 메서드 추가 LGTM(미세 개선 제안)

현재도 NPE 가능성은 낮지만, 방어적 비교로 가독성과 안전성을 높일 수 있습니다.

-    public boolean isOwnedBy(Long ownerId) {
-        return this.getOwner().getUserId().equals(ownerId);
-    }
+    public boolean isOwnedBy(Long ownerId) {
+        return java.util.Objects.equals(this.getOwner().getUserId(), ownerId);
+    }
src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java (1)

31-33: 벌크 삭제 시 1차 캐시 정합성 및 영향 행 수 반환 권장

다른 삭제 메서드와 동일하게 옵션/반환값을 통일해 운영 가시성을 확보하세요.

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM SavedFoodTruck sft WHERE sft.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    int deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

삭제 체인의 트랜잭션 경계(@transactional)와 FK 제약 순서(예: Rating → Reservation → Saved → Area → Menu → AvailableDate → FoodTruck)가 서비스에서 보장되는지도 최종 점검 부탁드립니다.

src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java (1)

25-27: 벌크 삭제 후 1차 캐시 동기화 보장 및 트랜잭션 확인 필요

JPQL 벌크 삭제는 영속성 컨텍스트를 우회합니다. clear/flush 자동화를 켜 두면 동일 트랜잭션 내 후속 조회/삭제 시 정합성 이슈를 줄일 수 있습니다. 또한 상위 서비스(@transactional) 경계에서 한 트랜잭션으로 묶이는지 확인 바랍니다.

최소 변경안:

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM Rating r WHERE r.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

선택사항(삭제 건수 관찰이 필요하다면 반환 타입 활용):

-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    int deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java (1)

12-16: 커서가 null일 때 첫 페이지가 비어질 수 있음 — JPQL 가드 추가 제안

:lastCursor 가 null이면 < :lastCursor 조건이 false가 되어 첫 페이지가 비게 될 수 있습니다. JPQL에서 null 가드를 추가해 주세요.

-    @Query("SELECT ft FROM FoodTruck ft " +
-            "WHERE ft.owner.userId = :ownerId " +
-            "AND ft.foodTruckId < :lastCursor " +
-            "ORDER BY ft.foodTruckId DESC")
+    @Query("SELECT ft FROM FoodTruck ft " +
+            "WHERE ft.owner.userId = :ownerId " +
+            "AND (:lastCursor IS NULL OR ft.foodTruckId < :lastCursor) " +
+            "ORDER BY ft.foodTruckId DESC")
     Slice<FoodTruck> findByOwnerUserIdWithCursor(@Param("ownerId") Long ownerId, @Param("lastCursor") Long lastCursor, Pageable pageable);

운영 팁: 오너별 커서 페이징이 빈번하다면 (owner_id, food_truck_id DESC) 복합 인덱스를 고려하세요.

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

11-13: 벌크 삭제 시 clear/flush 옵션 권장, 필요 시 삭제 건수 반환

동일 트랜잭션 내 후속 작업 정합성을 위해 clear/flush 자동화 권장. 필요하면 반환 타입으로 영향 행 수 관측 가능합니다.

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM AvailableDate ad WHERE ad.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

선택사항:

-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    int deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java (1)

11-13: 동일 패턴: 벌크 삭제 Modifying 옵션 정교화

다른 리포지토리와 동일하게 clear/flush 자동화 적용을 권장합니다.

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM Menu m WHERE m.foodTruck.foodTruckId = :foodTruckId")
-    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
+    void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)

112-118: Cursor 기본값 처리 일관성 유지 제안

위의 예약 조회처럼 request.pagingOrDefault()를 통해 기본 커서/사이즈 적용을 일관화하는 편이 안전합니다(서비스 단에서 이미 처리 중이라면 무시).

-    public CursorPagingResponse<MyFoodTruckResponse> getMyFoodTrucks(CursorPagingRequest request, Long ownerId) {
+    public CursorPagingResponse<MyFoodTruckResponse> getMyFoodTrucks(CursorPagingRequest request, Long ownerId) {
         // 사장님인지 먼저 검증
         ownerValidator.validateAndGetOwner(ownerId);

         // 사장님 - 나의 푸드트럭 목록 조회 로직 호출
-        return myFoodTruckService.getMyFoodTrucks(request, ownerId);
+        CursorPagingRequest paging = request.pagingOrDefault();
+        return myFoodTruckService.getMyFoodTrucks(paging, ownerId);
     }
src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java (1)

186-197: 미세 스타일 정리 및 상태코드 재검토(선택)

메서드 선언부 공백 제거 권장. 응답 래퍼 정책에 따르되, 가능하면 삭제는 204가 자연스럽습니다(팀 컨벤션 우선).

-    public BaseResponse<Void> deleteFoodTruck (
+    public BaseResponse<Void> deleteFoodTruck(
             @PathVariable final Long foodTruckId,
             @Parameter(hidden = true) @UserId final Long ownerId) {
         ownerService.deleteMyFoodTruck(ownerId, foodTruckId);
         return BaseResponse.ok(null);
     }
src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java (1)

13-16: 필드명 변경은 API 호환성 이슈 유발 — @JsonAlias로 하위호환 유지 권장

profileImage/name 변경은 기존 클라이언트(userProfileImage/username)와의 호환성을 깰 수 있습니다. 단기간 이행을 위해 Alias를 제안합니다.

 package konkuk.chacall.domain.owner.presentation.dto.response;

+import com.fasterxml.jackson.annotation.JsonAlias;
 import io.swagger.v3.oas.annotations.media.Schema;
 import konkuk.chacall.domain.reservation.domain.model.Reservation;
 import konkuk.chacall.domain.user.domain.model.User;

@@
         @Schema(description = "유저(고객) 프로필 이미지", example = "http://image.png")
-        String profileImage,
+        @JsonAlias("userProfileImage") String profileImage,
         @Schema(description = "유저(고객) 이름", example = "홍길동")
-        String name,
+        @JsonAlias("username") String name,
@@
-                dateTimeList,
-                reservation.getFoodTruck().getName()
+                dateTimeList,
+                reservation.getFoodTruck().getName()

FE/모바일 소비처가 모두 변경되었는지 확인 부탁드립니다(문서/스웨거 예시도 동기화).

Also applies to: 20-22, 33-35

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

35-37: 서비스 지역 문자열: null/중복/정렬 처리로 안정성·일관성 개선

Region 또는 fullName이 null인 경우 방어, 중복 제거 및 정렬로 응답 순서를 안정화하면 좋습니다.

- String serviceAreaString = serviceAreas.stream()
-         .map(serviceArea -> serviceArea.getRegion().getFullName())
-         .collect(Collectors.joining(", "));
+ String serviceAreaString = serviceAreas.stream()
+         .map(sa -> sa.getRegion())
+         .filter(java.util.Objects::nonNull)
+         .map(region -> region.getFullName())
+         .filter(java.util.Objects::nonNull)
+         .distinct()
+         .sorted()
+         .collect(Collectors.joining(", "));

11-22: Swagger 예시 값 현실화(문서 품질)

imageUrl 예시는 상대 경로(image.png)보다 절대 URL 예시가 소비자에게 명확합니다. 지역 표기 예시도 실제 도메인 표기 규칙(구·군·구역 구분자 등)에 맞춰 주면 좋습니다.

예:

  • imageUrl: https://cdn.example.com/foodtrucks/1/main.jpg
  • serviceArea: "서울 전체, 경기 수원시 영통구, 인천 계양구"
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (2)

41-53: 페이지 사이즈 검증/상한 설정 (악용 및 실수 방지)

request.size()가 0/음수/과대값일 경우 예외 또는 과부하 위험이 있습니다. 최소/최대 범위를 강제하세요.

- Slice<FoodTruck> foodTruckSlice = findFoodTrucks(ownerId, request.cursor(), request.size());
+ int size = Math.max(1, Math.min(request.size(), MAX_PAGE_SIZE));
+ Slice<FoodTruck> foodTruckSlice = findFoodTrucks(ownerId, request.cursor(), size);

클래스 상단에 상수 추가:

private static final int MAX_PAGE_SIZE = 50;

97-107: 빈 목록 최적화: IN () 쿼리 회피

푸드트럭 목록이 비어있으면 조기 반환하여 불필요한 IN () 쿼리를 피하세요. 일부 DB/JPQL 구현에서 문법 문제가 날 수 있습니다.

 private Map<Long, List<FoodTruckServiceArea>> getServiceAreaMap(List<FoodTruck> foodTrucks) {
-    List<Long> foodTruckIds = foodTrucks.stream()
+    if (foodTrucks.isEmpty()) {
+        return java.util.Map.of();
+    }
+    List<Long> foodTruckIds = foodTrucks.stream()
         .map(FoodTruck::getFoodTruckId)
         .toList();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 218e1f7 and 8d29a14.

📒 Files selected for processing (18)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java (1 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 (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (3 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java (1 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 (2 hunks)
  • src/main/java/konkuk/chacall/domain/region/domain/Region.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (2)
src/main/java/konkuk/chacall/global/common/exception/EntityNotFoundException.java (1)
  • EntityNotFoundException (6-11)
src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1)
  • RequiredArgsConstructor (26-92)
🔇 Additional comments (11)
src/main/java/konkuk/chacall/domain/region/domain/Region.java (1)

5-8: Getter 추가 LGTM

읽기 전용 접근 목적에 부합합니다. 추가 세터가 없어 엔티티 불변성도 유지됩니다.

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

12-16: API 필드명 변경에 따른 호환성 확인

userProfileImage→profileImage, username→name으로 변경되었습니다. 프론트/모바일 소비자와 스웨거 스키마, 스냅샷 테스트가 모두 갱신되었는지 확인해 주세요.

Also applies to: 46-49

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

84-92: 오너 푸드트럭 응답 스펙 추가 LGTM

에러 코드 구성이 실제 컨트롤러/서비스에서 던지는 예외와 일치하는지(특히 USER_FORBIDDEN vs. 소유권 불일치)만 재확인 부탁드립니다.

src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1)

53-53: 소유권 체크 메서드명 변경 반영 OK

의도 명확해졌습니다. 다른 호출부(히스토리 등)에서도 동일한 rename이 누락되지 않았는지 한 번만 점검해 주세요.

src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (1)

46-48: 소유권 체크 위임 명확 — LGTM

FoodTruck.isOwnedBy로 위임해 응집도/재사용성 좋아졌습니다.

src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java (1)

172-184: 엔드포인트 추가: 페이징 바인딩/위임 적절 — LGTM

요청 바인딩(@ParameterObject)과 서비스 위임이 간결합니다.

src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)

120-126: 확인: MyFoodTruckService.deleteMyFoodTruck는 메서드 수준 @transactional로 보호됩니다

  • 파일/위치: src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java — 클래스에 @transactional(readOnly = true) 선언(라인 30), 메서드에 @transactional 선언(라인 55–56).
  • 삭제 호출 순서 확인: ratingRepository.deleteAllByFoodTruckId(...) → reservationRepository.deleteAllByFoodTruckId(...). (라인 74–78)
  • 롤백 동작: 기본 Spring @transactional은 RuntimeException 및 Error에서 전체 롤백합니다. 체크 예외에서도 롤백이 필요하면 rollbackFor로 명시하세요.
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (3)

109-116: 정정 — N+1 우려 불필요(사진은 엔티티 관계가 아님)

FoodTruck.foodTruckPhotoList는 @convert(PhotoUrlListConverter.class)로 DB 컬럼에 저장되는 값 객체(PhotoUrlList)이며, MyFoodTruckResponse.of()가 호출하는 getMainPhotoUrl()은 이 값 객체의 메서드입니다. FoodTruckRepository.findByOwnerUserIdWithCursor 쿼리(SELECT ft FROM FoodTruck ft)는 FoodTruck 엔티티를 한 번에 조회하므로 OneToMany LAZY로 인한 트럭당 추가 쿼리(N+1)는 발생하지 않습니다.

참고: src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java, src/main/java/konkuk/chacall/domain/foodtruck/domain/value/PhotoUrlList.java, src/main/java/konkuk/chacall/global/common/converter/PhotoUrlListConverter.java, src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java

Likely an incorrect or invalid review comment.


90-92: 확인 — 커서 정렬 일치 (foodTruckId DESC / lastCursor 비교)

레포지토리 쿼리가 AND ft.foodTruckId < :lastCursorORDER BY ft.foodTruckId DESC로 고정되어 있어 커서 축(중복/누락 방지)이 일치합니다.
확인 파일: src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java


55-85: 삭제 순서·레퍼런셜 무결성 점검 — @Modifying 존재, DB FK/ON DELETE 미발견

결과: deleteAllByFoodTruckId는 여러 리포지토리에서 @Modifying JPQL DELETE로 구현되어 있고 MyFoodTruckService.deleteMyFoodTruck는 @transactional로 실행됩니다. 코드/마이그레이션에서 FOREIGN KEY / ON DELETE CASCADE 관련 SQL은 발견되지 않았습니다.

영향 파일(확인용):

  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java
  • src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java
  • src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java
  • src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java
  • src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java

권장 조치:

  • 운영 DB/마이그레이션에서 FK 제약·ON DELETE 존재 여부 직접 확인.
  • 없다면 DB단 ON DELETE CASCADE 도입 고려 또는 현재 방식 유지 시 각 @Modifying 삭제의 영향 행수(affected rows)를 로깅/메트릭으로 검증하고 실패 감지 로직 추가.
  • 동시성(예약 생성 경쟁) 완화를 위해 푸드트럭 비활성화 → 실제 삭제의 2단계 절차 검토.
src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java (1)

31-33: 대표 이미지: LAZY/N+1 걱정 불필요 — 값 객체(@convert)로 매핑되어 있음; 필요 시 DTO에서만 null‑safe 처리 적용

FoodTruck.foodTruckPhotoList는 PhotoUrlList(값 객체)로 @convert로 매핑되어 있고 @column(nullable = false)로 선언되어 있어 연관 엔티티가 아니므로 LAZY/N+1 또는 LazyInitializationException 우려 없음. PhotoUrlList.getMainPhotoUrl()는 내부 리스트가 비어있으면 null을 반환함.

검토된 호출 지점: src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java, src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java, src/main/java/konkuk/chacall/domain/member/presentation/dto/response/ReservationForRatingResponse.java

권장(선택): 데이터 무결성 우려가 있으면 DTO에서만 간단히 null‑safe 처리 적용.
예:
String mainImageUrl = foodTruck.getFoodTruckPhotoList() != null
? foodTruck.getFoodTruckPhotoList().getMainPhotoUrl()
: null;

Likely an incorrect or invalid review comment.

buzz0331
buzz0331 previously approved these changes Sep 18, 2025

@buzz0331 buzz0331 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.

굿굿 성능 + 가독성 모두 챙긴 너무 꼼꼼한 코드라 완벽하네요! 몇가지 사소한 리뷰 남겼으니 확인부탁드려요~ 👍🏻

Comment on lines +97 to +115
private Map<Long, List<FoodTruckServiceArea>> getServiceAreaMap(List<FoodTruck> foodTrucks) {
List<Long> foodTruckIds = foodTrucks.stream()
.map(FoodTruck::getFoodTruckId)
.toList();

List<FoodTruckServiceArea> serviceAreas = foodTruckServiceAreaRepository
.findAllWithRegionByFoodTruckIdIn(foodTruckIds);

return serviceAreas.stream()
.collect(Collectors.groupingBy(sa -> sa.getFoodTruck().getFoodTruckId()));
}

private List<MyFoodTruckResponse> mapToMyFoodTruckResponse(List<FoodTruck> foodTrucks, Map<Long, List<FoodTruckServiceArea>> serviceAreaMap) {
return foodTrucks.stream()
.map(foodTruck -> {
List<FoodTruckServiceArea> serviceAreas = serviceAreaMap.getOrDefault(foodTruck.getFoodTruckId(), List.of());
return MyFoodTruckResponse.of(foodTruck, serviceAreas);
})
.toList();
}

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.

stream 활용이 깔끔하네요! LGTM

Comment on lines +61 to +62
if (!foodTruck.isOwnedBy(ownerId)) {
throw new BusinessException(ErrorCode.USER_FORBIDDEN);
}

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.

p2: 에러 메시지를 FOODTRUCK_FORBIDDEN으로 하는거 어떨까요? USER_FORBIDDEN은 현재 유저 역할을 검증하는 에러메시지로 사용되고 있어서 메시지 의미가 겹칠 것 같습니다!

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 +74 to +77
// 푸드트럭 관련 평점 삭제
ratingRepository.deleteAllByFoodTruckId(foodTruckId);

// 푸드트럭 관련 예약 삭제
reservationRepository.deleteAllByFoodTruckId(foodTruckId);

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.

rating -> reservation 순서 꼼꼼하게 해주셨네요~ 역시 꼼신이요 👍🏻

Comment on lines +34 to +37
// 서비스 지역: Region 의 fullName 을 ", "로 연결하여 하나의 문자열로 만듦
String serviceAreaString = serviceAreas.stream()
.map(serviceArea -> serviceArea.getRegion().getFullName())
.collect(Collectors.joining(", "));

@buzz0331 buzz0331 Sep 18, 2025

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.

p3: 이것도 dto에서 많이 쓰이게 될 응답 형식일 것 같은데 도메인 내부에서 해결하는거 어떨까요??

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.

좋습니다~

@ksg1227 ksg1227 Sep 18, 2025

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.

@buzz0331

다만 이 로직에서 좀 아쉬운 점이 있는데, 현재 저희 프로젝트와 같이 단방향 연관관계만을 유지할 경우, 엔티티 내부의 필드만을 활용해서는 이러한 호출 가능 지역 문자열을 만들어내지 못하는 상황이네요...
FoodTruckServiceArea 엔티티 내부에서 문자열을 만들어내자니, 해당 클래스 내부에는 List 이 아닌 오직 Region 객체 하나만을 필드로 갖기 때문에 불가하고,
FoodTruck 내부에서 문자열을 만들어내자니, 양방향 연관관계가 존재하지 않아서 그것도 불가능하네요.

그래서 일단은 foodTruck 내부에서 이러한 호출 가능 지역 문자열을 만들어내는 것이 가장 적합하다고 판단되어서

public class FoodTruck .. {

    public String getServiceAreas(List<FoodTruckServiceArea> serviceAreaList) {
        return serviceAreaList.stream()
                .map(serviceArea -> serviceArea.getRegion().getFullName())
                .collect(Collectors.joining(", "));
    }
}

이런 식으로 FoodTruck 내부의 필드를 활용하지는 않고, 외부에서 해당 푸드트럭에 대한 FoodTruckServiceArea 리스트를 받고, 메서드 내부에서 문자열을 만들어주는 식으로 로직을 구성하고자 합니다.
다른 방법은 애초부터 List< FoodTruckServiceArea > 를 받는 것이 아니라, List< Region > 을 받아서 처리할 수도 있긴 할 것 같습니다.

이 부분에 대해서 어떻게 생각하시는지 의견이 궁금합니다!!

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.

오호 양방향이 없으니 이런 이슈가 있군요. 음 그러면 단순히 응답 형식을 파싱하는 역할을 FoodTruck에게 위임하는 식이네요. 좋은 것 같습니다. 사실상 static 메서드라 static으로 선언해도 무방할 것 같긴한데 이 부분은 상균님이 편하실대로 해주시면 될 것 같습니다! 저도 그에 맞춰서 구현하겠습니다~

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.

넵! 제 생각에도 사실상 파싱용 static 메서드라고 볼 수 있을 것 같습니다.
다만 로직 자체가 특정 푸드트럭의 호출 가능 지역 을 호출하는 것이기 때문에 static 으로 선언하는 것보다는,
푸드트럭 변수.getServiceAreas() 와 같은 형태가 좀 더 의미상 적합할 것 같다고 판단되어서 인스턴스 메서드로 구현하겠습니다~

@ksg1227 ksg1227 force-pushed the feat/#16-owner-foodtruck-read-delete branch from 8d29a14 to 8e94e4e Compare September 18, 2025 17:35

@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

🧹 Nitpick comments (8)
src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (2)

76-78: NPE 방지: equals 호출을 Objects.equals로 교체 권장

ownerId가 null일 경우 NPE 가능성이 있습니다(현재 호출부에서 null 아닐 가능성이 높지만 방어 코드 권장).

적용 diff:

-    public boolean isOwnedBy(Long ownerId) {
-        return this.getOwner().getUserId().equals(ownerId);
-    }
+    public boolean isOwnedBy(Long ownerId) {
+        return java.util.Objects.equals(this.getOwner().getUserId(), ownerId);
+    }

(별도 import를 추가하지 않으려면 위와 같이 정규명 사용, 또는 import java.util.Objects; 추가)


84-89: 서비스 지역 문자열의 결정적 순서/중복 제거 제안

표시 순서가 비결정적일 수 있습니다. UX 일관성을 위해 정렬 및 중복 제거를 권장합니다.

적용 diff:

-    public String getServiceAreas(List<FoodTruckServiceArea> serviceAreaList) {
-        return serviceAreaList.stream()
-                .map(serviceArea -> serviceArea.getRegion().getFullName())
-                .collect(Collectors.joining(", "));
-    }
+    public String getServiceAreas(List<FoodTruckServiceArea> serviceAreaList) {
+        return serviceAreaList.stream()
+                .map(sa -> sa.getRegion().getFullName())
+                .filter(java.util.Objects::nonNull)
+                .distinct()
+                .sorted(String::compareToIgnoreCase)
+                .collect(Collectors.joining(", "));
+    }

추가로, 이 로직은 DTO 매핑 계층에 위치시키는 것이 도메인 모델의 순수성을 유지하는 데 더 적합합니다(선택 사항).

src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java (1)

47-49: 벌크 삭제 후 영속성 컨텍스트 불일치 방지: @Modifying 옵션 추가 권장

벌크 DML은 엔티티 상태와 불일치할 수 있습니다. 자동 플러시/클리어를 켜 두면 후속 연산 안정성이 높아집니다.

적용 diff:

-    @Modifying
+    @Modifying(clearAutomatically = true, flushAutomatically = true)
     @Query("DELETE FROM Reservation r WHERE r.foodTruck.foodTruckId = :foodTruckId")
     void deleteAllByFoodTruckId(@Param("foodTruckId") Long foodTruckId);

(선택) 반환형을 int로 바꿔 삭제 카운트를 로깅/검증에 활용하는 것도 좋습니다.

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

10-23: nullable 스키마 명시 제안.

imageUrl, description, activeTime, serviceArea가 null 가능하다면 OpenAPI에 nullable = true를 명시해 클라이언트 기대치와 문서를 일치시키는 것이 좋습니다.

-        @Schema(description = "푸드트럭 이미지", example = "image.png")
+        @Schema(description = "푸드트럭 이미지", example = "image.png", nullable = true)
         String imageUrl,
-        @Schema(description = "푸드트럭 설명", example = "저희 푸드트럭은 10년간 이어져온...")
+        @Schema(description = "푸드트럭 설명", example = "저희 푸드트럭은 10년간 이어져온...", nullable = true)
         String description,
-        @Schema(description = "운영 가능 시간대", example = "09:00 ~ 21:00")
+        @Schema(description = "운영 가능 시간대", example = "09:00 ~ 21:00", nullable = true)
         String activeTime,
-        @Schema(description = "호출 가능 지역", example = "서울 전체, 경기도 수원시 영통구, 인천 계양구")
+        @Schema(description = "호출 가능 지역(쉼표로 구분된 문자열)", example = "서울 전체, 경기도 수원시 영통구, 인천 계양구", nullable = true)
         String serviceArea

21-22: 필드명 의미 명확화(선택).

serviceArea에는 복수 값이 콤마로 들어갑니다. API 일관성을 위해 serviceAreas(문자열) 또는 serviceAreas: List<String>로 바꾸는 것을 고려해보세요. 변경이 부담되면 @Schema 설명에 “복수 지역의 콤마 구분 문자열”임을 명시하는 정도로도 충분합니다.

src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (3)

40-52: 빈 결과 시 불필요 쿼리 방지 및 RDB ‘IN ()’ 위험 방어.

foodTrucks가 비면 findAllWithRegionByFoodTruckIdIn(empty) 호출을 생략하세요. 일부 JPA 구현/방언에서 빈 IN 절이 SQL 오류가 될 수 있고, 어쨌든 불필요한 DB 왕복입니다.

         Slice<FoodTruck> foodTruckSlice = findFoodTrucks(ownerId, request.cursor(), request.size());
         List<FoodTruck> foodTrucks = foodTruckSlice.getContent();
 
+        if (foodTrucks.isEmpty()) {
+            return CursorPagingResponse.of(List.of(), MyFoodTruckResponse::foodTruckId, false);
+        }
+
         // 2. 호출 가능 지역 정보 Map 조회
         Map<Long, List<FoodTruckServiceArea>> serviceAreaMap = getServiceAreaMap(foodTrucks);

28-31: 읽기 기본 트랜잭션 설정(선택).

클래스 레벨에 @Transactional(readOnly = true)를 부여하고, 쓰기 메서드만 override로 @Transactional을 두면 JPA 플러시/락 비용을 줄이고 실수로 인한 쓰기를 방지할 수 있습니다.

 @RequiredArgsConstructor
 @Service
+@Transactional(readOnly = true)
 public class MyFoodTruckService {

70-72: 주석 오탈자.

“가능한 일정대” → “가능 일정 데이터” 또는 “가능한 일정 데이터”.

-        // 푸드트럭 가능한 일정대 삭제
+        // 푸드트럭 가능 일정 데이터 삭제
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d29a14 and 8e94e4e.

📒 Files selected for processing (21)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/FoodTruck.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java (1 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 (1 hunks)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java (2 hunks)
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java (1 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 (2 hunks)
  • src/main/java/konkuk/chacall/domain/region/domain/Region.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java (1 hunks)
  • src/main/java/konkuk/chacall/domain/reservation/domain/repository/ReservationRepository.java (2 hunks)
  • src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (1 hunks)
  • src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java
  • src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/AvailableDateRepository.java
  • src/main/java/konkuk/chacall/domain/member/domain/repository/SavedFoodTruckRepository.java
  • src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckServiceAreaRepository.java
  • src/main/java/konkuk/chacall/domain/member/domain/repository/RatingRepository.java
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationDetailResponse.java
  • src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java
  • src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java
  • src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/MenuRepository.java
  • src/main/java/konkuk/chacall/domain/region/domain/Region.java
  • src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/OwnerReservationHistoryResponse.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (2)
src/main/java/konkuk/chacall/global/common/exception/EntityNotFoundException.java (1)
  • EntityNotFoundException (6-11)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)
  • RequiredArgsConstructor (19-129)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1)
  • RequiredArgsConstructor (28-118)
🔇 Additional comments (6)
src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java (1)

71-73: FOOD_TRUCK_NOT_OWNED 추가 적절 — 403/404 정책 일관성만 확인해 주세요

소유권 오류 분기 명확해져서 👍. 다만 동일 맥락(예약 등)에서 403 vs 404 응답 정책을 일관되게 유지하는지와 스웨거 문서 반영 여부를 확인 부탁드립니다.

src/main/java/konkuk/chacall/domain/owner/application/reservation/OwnerReservationService.java (1)

51-53: 소유권 체크 메서드 변경 반영 확인 — 레거시 호출 없음

Reservation.java에 isForFoodTruckOwnedBy(Long ownerId) 정의(반환: foodTruck.isOwnedBy(ownerId))가 존재하며, 전역 검색에서 기존 reservation.isOwnedBy(...) 호출은 발견되지 않았습니다.
src/main/java/konkuk/chacall/domain/reservation/domain/model/Reservation.java:46-48

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

34-41: DTO 매핑 전반 LGTM.

도메인 메서드 위임(foodTruck.getServiceAreas(serviceAreas))으로 표현 책임을 정리한 점 좋습니다.

src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (3)

108-115: 스트림 매핑 LGTM.

없을 때 List.of()로 안전 처리한 점 좋습니다.


54-84: 확인: deleteAllByFoodTruckId는 벌크 DELETE(@Modifying + JPQL)로 구현되어 있습니다.

MenuRepository, FoodTruckServiceAreaRepository, AvailableDateRepository, ReservationRepository, SavedFoodTruckRepository, RatingRepository에서 @Modifying과 JPQL DELETE 쿼리(반환 void)로 정의되어 있으며, MyFoodTruckService.deleteMyFoodTruck는 @transactional로 감싸져 있어 트랜잭션 내에서 벌크 삭제로 실행됩니다.


51-52: 확인 완료 — 커서 정렬이 foodTruckId 기준으로 일관됩니다.
findByOwnerUserIdWithCursor 쿼리에 AND ft.foodTruckId < :lastCursor 및 ORDER BY ft.foodTruckId DESC가 포함되어 있어 CursorPagingResponse.of(..., MyFoodTruckResponse::foodTruckId, ...) 사용과 일치합니다. (src/main/java/konkuk/chacall/domain/foodtruck/domain/repository/FoodTruckRepository.java)

Comment thread src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java Outdated
Comment on lines +31 to +33
// 대표 이미지
String mainImageUrl = foodTruck.getFoodTruckPhotoList().getMainPhotoUrl();

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

대표 이미지 NPE 가능성(차단 필요).

foodTruck.getFoodTruckPhotoList() 또는 그 내부 값이 없으면 NPE 발생 여지가 큽니다(초기 이미지 미등록 케이스 등). 아래처럼 null‑safe 처리로 방어해주세요.

-                String mainImageUrl = foodTruck.getFoodTruckPhotoList().getMainPhotoUrl();
+                String mainImageUrl = foodTruck.getFoodTruckPhotoList() != null
+                        ? foodTruck.getFoodTruckPhotoList().getMainPhotoUrl()
+                        : null;
📝 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 mainImageUrl = foodTruck.getFoodTruckPhotoList().getMainPhotoUrl();
// 대표 이미지
String mainImageUrl = foodTruck.getFoodTruckPhotoList() != null
? foodTruck.getFoodTruckPhotoList().getMainPhotoUrl()
: null;
🤖 Prompt for AI Agents
In
src/main/java/konkuk/chacall/domain/owner/presentation/dto/response/MyFoodTruckResponse.java
around lines 31–33, the code calls
foodTruck.getFoodTruckPhotoList().getMainPhotoUrl() which can throw an NPE if
getFoodTruckPhotoList() returns null or if the main photo is missing; change
this to a null-safe retrieval: first check whether
foodTruck.getFoodTruckPhotoList() is non-null (and contains a main photo), and
only then call getMainPhotoUrl(); otherwise set mainImageUrl to null or a
sensible default (or use Optional.ofNullable(...) with map(...).orElse(null)) so
the code never dereferences a null object.

buzz0331
buzz0331 previously approved these changes Sep 18, 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: 1

🧹 Nitpick comments (3)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (2)

63-83: 삭제 작업 실패 시 부분 삭제 방지 확인

여러 repository의 deleteAll 메서드가 순차적으로 호출되는데, 중간에 실패할 경우를 대비한 트랜잭션 롤백이 제대로 동작하는지 확인이 필요합니다. 특히 일부 테이블에만 데이터가 삭제되는 상황을 방지해야 합니다.

각 삭제 작업 후 로깅을 추가하여 디버깅과 모니터링을 용이하게 하는 것을 권장합니다:

+        log.debug("Deleting food truck service areas for foodTruckId: {}", foodTruckId);
         foodTruckServiceAreaRepository.deleteAllByFoodTruckId(foodTruckId);

+        log.debug("Deleting menus for foodTruckId: {}", foodTruckId);
         menuRepository.deleteAllByFoodTruckId(foodTruckId);

95-105: 빈 리스트 처리 최적화 가능

foodTrucks가 비어있을 때도 DB 쿼리가 실행됩니다. 성능 최적화를 위해 빈 리스트 체크를 추가하는 것을 고려해보세요.

 private Map<Long, List<FoodTruckServiceArea>> getServiceAreaMap(List<FoodTruck> foodTrucks) {
+    if (foodTrucks.isEmpty()) {
+        return Map.of();
+    }
+    
     List<Long> foodTruckIds = foodTrucks.stream()
             .map(FoodTruck::getFoodTruckId)
             .toList();
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)

131-138: 트랜잭션 전파 확인 — MyFoodTruckService에 명시적 @transactional 추가 권장

검증 결과: MyFoodTruckService.deleteMyFoodTruck에는 @transactional이 없고, OwnerService.deleteMyFoodTruck는 @transactional으로 선언되어 있어 현재 호출 경로에서는 트랜잭션이 전파됩니다.
권장 조치: 재사용성과 명시성을 위해 MyFoodTruckService.deleteMyFoodTruck(또는 서비스 클래스)에 @transactional을 추가하거나 해당 메서드가 트랜잭션이 필요함을 문서화하세요.
대상: src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e94e4e and 9d404b6.

📒 Files selected for processing (4)
  • src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (11 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java (0 hunks)
  • src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/konkuk/chacall/domain/owner/application/chatTemplate/ChatTemplateService.java
  • src/main/java/konkuk/chacall/domain/owner/application/bankAccount/BankAccountService.java
🧰 Additional context used
🧬 Code graph analysis (2)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (2)
src/main/java/konkuk/chacall/global/common/exception/EntityNotFoundException.java (1)
  • EntityNotFoundException (6-11)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)
  • RequiredArgsConstructor (19-139)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (1)
src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1)
  • RequiredArgsConstructor (28-117)
🔇 Additional comments (3)
src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java (2)

122-129: LGTM! 읽기 전용 트랜잭션 설정이 적절합니다

조회 메서드에 @Transactional(readOnly = true)를 적용한 것은 성능 최적화 관점에서 좋은 선택입니다.


31-38: 트랜잭션 전파 검토 불필요

BankAccountService.registerBankAccount에는 별도의 @Transactional이 선언되어 있지 않으므로 내부 호출은 상위 트랜잭션에 그대로 참여합니다. 별도 전파 속성 지정이나 추가 검증이 필요 없습니다.

Likely an incorrect or invalid review comment.

src/main/java/konkuk/chacall/domain/owner/application/myFoodTruck/MyFoodTruckService.java (1)

72-77: 검증 완료 — 삭제 순서(평점 → 예약) 유지

  • Rating은 Reservation을 FK(reservation_id)로 참조함. (src/main/java/konkuk/chacall/domain/member/domain/Rating.java)
  • RatingRepository와 ReservationRepository는 각각 JPQL bulk DELETE(@Modifying "DELETE FROM Rating ...", "DELETE FROM Reservation ...")를 사용하며, OwnerService.deleteMyFoodTruck은 @transactional로 호출되어 동일 트랜잭션 내에서 실행됩니다.
  • 코드베이스에서 Reservation↔Rating 사이 cascade/orphanRemoval/@onDelete 설정은 없음(검색 결과).
  • 결론: ratingRepository.deleteAllByFoodTruckId(...) → reservationRepository.deleteAllByFoodTruckId(...) 순서는 적절합니다.

buzz0331
buzz0331 previously approved these changes Sep 19, 2025
@@ -0,0 +1,118 @@
package konkuk.chacall.domain.owner.application.myFoodTruck;

@buzz0331 buzz0331 Sep 19, 2025

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.

제가 알기론 자바에서 패키지 네이밍은 클래스와 구분하기 위해 소문자로 통일하는 것으로 아는데 저희는 패키지 네이밍 컨벤션을 camel case로 통일하는걸로 하는건가욥??

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.

아 그렇네요 소문자로 통일하겠슴다

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