-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT] 푸드트럭 정보 등록/수정 api 구현 #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fd0dbaa
91afd60
e8fa914
b6e9dc7
03d0e56
21cbb56
4cad11e
f832e70
96f3445
c8f2e44
1431dcb
6b1fb5a
ade62c6
d18efd3
88348db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| 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()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 푸드트럭 노출 상태 변경 허용 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 운영 가능 날짜 동기화: null/검증 누락
아래처럼 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
|---|---|---|
|
|
@@ -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 { | ||
|
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 날짜 유효성(시작 ≤ 종료) 검증 누락 기간 엔티티에서 정적 팩토리에서 사전 검증을 추가해 주세요: - 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();
}해당 테이블에 🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
부분 실패 시나리오에 대한 에러 처리를 개선하세요.
현재 구현은 여러 이미지를 삭제하는 과정에서 중간에 실패가 발생하면 나머지 삭제가 시도되지 않을 수 있습니다. 다음 사항을 고려해보세요:
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
🤖 Prompt for AI Agents