Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
fd0dbaa
[refactor] 전기 사용 여부 가능/불가능에서 필요/불필요로 단어 수정 (#49)
buzz0331 Oct 21, 2025
91afd60
[refactor] ddl-auto 다시 update로 수정 (#49)
buzz0331 Oct 21, 2025
e8fa914
[refactor] FoodTruck에서 FoodTruckInfo 객체 추출 (#49)
buzz0331 Oct 21, 2025
b6e9dc7
[feat] 푸드트럭 정보기입/수정 api dto 선언 (#49)
buzz0331 Oct 21, 2025
03d0e56
[feat] 푸드트럭 정보기입/수정 api 핸들러 선언 (#49)
buzz0331 Oct 21, 2025
21cbb56
[refactor] 푸드트럭 정보기입이 되어있어야 노출 상태 변경이 가능하도록 수정 (컬럼추가) (#49)
buzz0331 Oct 21, 2025
4cad11e
[feat] json body에서 enum 자동 매핑되도록 수정 (#49)
buzz0331 Oct 21, 2025
f832e70
[feat] S3에서 푸드트럭 이미지 객체를 삭제하는 api 구현 (#49)
buzz0331 Oct 21, 2025
96f3445
[feat] 푸드트럭 정보 등록/수정 api 서비스 로직 구현 (#49)
buzz0331 Oct 21, 2025
c8f2e44
[docs] 스웨거 예외 명세 추가 (#49)
buzz0331 Oct 21, 2025
1431dcb
[refactor] 메뉴 조회에서 메뉴 가격 정수형으로 수정 (#49)
buzz0331 Oct 24, 2025
6b1fb5a
[refactor] 메뉴 조회에서 메뉴 가격 정수형으로 수정 (#49)
buzz0331 Oct 25, 2025
ade62c6
[refactor] 제조 수량에서 '200인분 미만' 추가하기 (#49)
buzz0331 Oct 25, 2025
d18efd3
[refactor] 일반유저 예약 상세조회에서 리뷰 필요 여부 반환 (#49)
buzz0331 Oct 25, 2025
88348db
[refactor] s3에서 메뉴 이미지 객체 삭제도 별도 API로 분리 (#49)
buzz0331 Oct 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package konkuk.chacall.domain.foodtruck.application;

import konkuk.chacall.domain.foodtruck.application.image.FoodTruckImageService;
import konkuk.chacall.domain.foodtruck.application.command.FoodTruckCommandService;
import konkuk.chacall.domain.foodtruck.application.info.FoodTruckInfoService;
import konkuk.chacall.domain.foodtruck.application.menu.FoodTruckMenuService;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckMenuRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckNameDuplicateCheckRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckMenuResponse;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckNameDuplicateCheckResponse;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.*;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.*;
import konkuk.chacall.domain.owner.application.validator.OwnerValidator;
import konkuk.chacall.domain.user.domain.model.User;
import konkuk.chacall.domain.member.application.validator.MemberValidator;
Expand All @@ -24,25 +18,24 @@
@Transactional(readOnly = true)
public class FoodTruckService {

private final FoodTruckCommandService foodTruckCommandService;
private final FoodTruckInfoService foodTruckInfoService;
private final FoodTruckMenuService foodTruckMenuService;
private final FoodTruckImageService foodTruckImageService;


private final MemberValidator memberValidator;
private final OwnerValidator ownerValidator;

public CursorPagingResponse<FoodTruckResponse> getFoodTrucks(Long memberId, FoodTruckSearchRequest request) {
memberValidator.validateAndGetMember(memberId);

return foodTruckCommandService.getFoodTrucks(memberId, request);
return foodTruckInfoService.getFoodTrucks(memberId, request);
}

public FoodTruckNameDuplicateCheckResponse isNameDuplicated(Long ownerId, FoodTruckNameDuplicateCheckRequest request) {
ownerValidator.validateAndGetOwner(ownerId);

return FoodTruckNameDuplicateCheckResponse.of(
foodTruckCommandService.isNameDuplicated(request.name()));
foodTruckInfoService.isNameDuplicated(request.name()));
}

public CursorPagingResponse<FoodTruckMenuResponse> getFoodTruckMenus(Long memberId, Long foodTruckId, FoodTruckMenuRequest request) {
Expand All @@ -63,7 +56,18 @@ public ImageResponse createMenuImagePresignedUrl(ImageRequest request, Long owne
return foodTruckImageService.createMenuImagePresignedUrl(request, owner);
}

@Transactional
public FoodTruckIdResponse updateMyFoodTruckInfo(Long ownerId, Long foodTruckId, UpdateFoodTruckInfoRequest request) {
User owner = ownerValidator.validateAndGetOwner(ownerId);

return FoodTruckIdResponse.of(
foodTruckInfoService.updateMyFoodTruckInfo(owner, foodTruckId, request)
);
}

public void deleteFoodTruckImagesFromS3(Long ownerId, Long foodTruckId, DeleteFoodTruckImagesRequest request) {
User owner = ownerValidator.validateAndGetOwner(ownerId);

foodTruckImageService.deleteFoodTruckImagesFromS3(owner, foodTruckId, request);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package konkuk.chacall.domain.foodtruck.application.image;

import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck;
import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.DeleteFoodTruckImagesRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse;
import konkuk.chacall.domain.user.domain.model.User;
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.AllowedFileExtension;
import konkuk.chacall.global.common.storage.util.CdnUrlResolver;
import konkuk.chacall.global.common.storage.util.KeyUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -18,7 +22,9 @@
public class FoodTruckImageService {

private final S3Service s3Service;
private final CdnUrlResolver cdnUrlResolver;
private static final int MAX_FOOD_TRUCK_IMAGE_COUNT = 9;
private final FoodTruckRepository foodTruckRepository;
// private static final int MAX_MENU_IMAGE_COUNT = 5;

public ImageResponse createFoodTruckImagePresignedUrl(ImageRequest request, User owner) {
Expand Down Expand Up @@ -65,4 +71,15 @@ public ImageResponse createMenuImagePresignedUrl(ImageRequest request, User owne

return ImageResponse.of(imageInfos);
}

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);
}
Comment on lines +75 to +84

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.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package konkuk.chacall.domain.foodtruck.application.info;

import konkuk.chacall.domain.foodtruck.domain.model.AvailableDate;
import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck;
import konkuk.chacall.domain.foodtruck.domain.model.FoodTruckServiceArea;
import konkuk.chacall.domain.foodtruck.domain.repository.AvailableDateRepository;
import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository;
import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckServiceAreaRepository;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.DateRangeRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.FoodTruckSearchRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.request.UpdateFoodTruckInfoRequest;
import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse;
import konkuk.chacall.domain.member.domain.repository.SavedFoodTruckRepository;
import konkuk.chacall.domain.region.domain.model.Region;
import konkuk.chacall.domain.region.domain.repository.RegionRepository;
import konkuk.chacall.domain.user.domain.model.User;
import konkuk.chacall.global.common.dto.CursorPagingResponse;
import konkuk.chacall.global.common.exception.EntityNotFoundException;
import konkuk.chacall.global.common.exception.code.ErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class FoodTruckInfoService {

private final FoodTruckRepository foodTruckRepository;
private final SavedFoodTruckRepository savedFoodTruckRepository;
private final FoodTruckServiceAreaRepository foodTruckServiceAreaRepository;
private final AvailableDateRepository availableDateRepository;
private final RegionRepository regionRepository;

public CursorPagingResponse<FoodTruckResponse> getFoodTrucks(Long memberId, FoodTruckSearchRequest request) {

Slice<FoodTruck> foodTruckSlice = foodTruckRepository.getFoodTrucks(request);
List<FoodTruck> content = foodTruckSlice.getContent();

// 이 페이지의 트럭 ID 목록
List<Long> foodTruckIds = content.stream()
.map(FoodTruck::getFoodTruckId)
.toList();

Set<Long> savedFoodTruckIds = foodTruckIds.isEmpty() ? Set.of() : savedFoodTruckRepository.findSavedTruckIdsIn(memberId, foodTruckIds);

List<FoodTruckResponse> foodTrucks = content.stream()
.map(ft -> FoodTruckResponse.of(ft, savedFoodTruckIds.contains(ft.getFoodTruckId())))
.toList();

return CursorPagingResponse.of(
foodTrucks,
FoodTruckResponse::foodTruckId,
foodTruckSlice.hasNext()
);
}

public boolean isNameDuplicated(String name) {
return foodTruckRepository.existsByFoodTruckInfo_Name(name);
}

public Long updateMyFoodTruckInfo(User owner, Long foodTruckId, UpdateFoodTruckInfoRequest request) {
FoodTruck foodTruck = foodTruckRepository.findById(foodTruckId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.FOOD_TRUCK_NOT_FOUND));

// 푸드트럭 소유주/승인 상태 검증
foodTruck.validateOwner(owner.getUserId());
foodTruck.validateApprovedStatus();

// 푸드트럭 정보 업데이트
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.

굿굿

foodTruck.permitChangeViewStatus();

// 서비스 가능 지역 동기화
syncServiceAreas(foodTruck, request.foodTruckServiceAreas());

// 운영 가능 날짜 동기화
syncAvailableDates(foodTruck, request.availableDates());

return foodTruck.getFoodTruckId();
}

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);
}

Comment on lines +90 to +101

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.

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))
Comment on lines +102 to +120

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.

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

.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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import jakarta.persistence.*;
import konkuk.chacall.global.common.domain.BaseEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;

import java.time.LocalDate;

@Builder
@Entity
@Table(name = "available_dates")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class AvailableDate extends BaseEntity {
Expand All @@ -29,4 +29,11 @@ public class AvailableDate extends BaseEntity {
@JoinColumn(name = "food_truck_id", nullable = false)
private FoodTruck foodTruck;

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

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.

}
Loading