diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java index 332c5dc4..d9c6b65c 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java @@ -3,6 +3,14 @@ import konkuk.chacall.domain.foodtruck.application.image.FoodTruckImageService; 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.global.common.storage.dto.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.global.common.storage.dto.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; diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java b/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java index 5f94834a..3aa5431d 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/image/FoodTruckImageService.java @@ -3,73 +3,45 @@ 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.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; +import konkuk.chacall.domain.user.domain.model.User; +import konkuk.chacall.global.common.storage.presign.PresignedUrlService; import konkuk.chacall.global.common.storage.util.CdnUrlResolver; import konkuk.chacall.global.common.storage.util.KeyUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import java.util.List; - @Service @RequiredArgsConstructor public class FoodTruckImageService { - private final S3Service s3Service; - private final CdnUrlResolver cdnUrlResolver; + private final PresignedUrlService presignedUrlService; private static final int MAX_FOOD_TRUCK_IMAGE_COUNT = 9; + private final FoodTruckRepository foodTruckRepository; -// private static final int MAX_MENU_IMAGE_COUNT = 5; + private final S3Service s3Service; + private final CdnUrlResolver cdnUrlResolver; public ImageResponse createFoodTruckImagePresignedUrl(ImageRequest request, User owner) { - List fileExtensions = request.fileExtensions(); - - if(fileExtensions.size() > MAX_FOOD_TRUCK_IMAGE_COUNT) { - throw new BusinessException(ErrorCode.INVALID_IMAGE_COUNT, - new IllegalArgumentException("푸드트럭 이미지는 1장 이상 " + MAX_FOOD_TRUCK_IMAGE_COUNT + "장 이하로 등록해야 합니다. 입력된 이미지 개수: " + fileExtensions.size())); - } - - AllowedFileExtension.checkAllowedExtension(fileExtensions); - - var imageInfos = fileExtensions.stream() - .map(extension -> { - String baseKey = KeyUtils.buildFoodTruckImageKey(owner.getUserId()); - String keyWithExt = baseKey + "." + extension.toLowerCase(); - - String presignedUrl = s3Service.generatePresignedUrl(keyWithExt); - String fileUrl = s3Service.getFileUrl(keyWithExt); - - return ImageResponse.ImageInfo.of(presignedUrl, fileUrl); - }) - .toList(); - - return ImageResponse.of(imageInfos); + return presignedUrlService.generatePresignedUrls( + request, + owner.getUserId(), + MAX_FOOD_TRUCK_IMAGE_COUNT, + KeyUtils::buildFoodTruckImageKey + ); } public ImageResponse createMenuImagePresignedUrl(ImageRequest request, User owner) { - List fileExtensions = request.fileExtensions(); - - AllowedFileExtension.checkAllowedExtension(fileExtensions); - - var imageInfos = fileExtensions.stream() - .map(extension -> { - String baseKey = KeyUtils.buildMenuImageKey(owner.getUserId()); - String keyWithExt = baseKey + "." + extension.toLowerCase(); - - String presignedUrl = s3Service.generatePresignedUrl(keyWithExt); - String fileUrl = s3Service.getFileUrl(keyWithExt); - - return ImageResponse.ImageInfo.of(presignedUrl, fileUrl); - }) - .toList(); - - return ImageResponse.of(imageInfos); + return presignedUrlService.generatePresignedUrls( + request, + owner.getUserId(), + null, + KeyUtils::buildMenuImageKey + ); } public void deleteFoodTruckImagesFromS3(User owner, Long foodTruckId, DeleteFoodTruckImagesRequest request) { diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java index 2ba5bbb9..fb6b62cb 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/domain/model/FoodTruck.java @@ -7,6 +7,7 @@ import konkuk.chacall.global.common.exception.DomainRuleException; import konkuk.chacall.global.common.exception.code.ErrorCode; import lombok.*; +import org.hibernate.annotations.ColumnDefault; import java.util.List; import java.util.stream.Collectors; @@ -44,9 +45,8 @@ public class FoodTruck extends BaseEntity { @Column(nullable = false, length = 3) private FoodTruckViewedStatus foodTruckViewedStatus = FoodTruckViewedStatus.OFF; - //todo 컬럼 추가 후 기존 데이터 변경한 다음 다시 주석 해제할 예정 @Builder.Default -// @Column(nullable = false) + @ColumnDefault("false") private Boolean canChangeViewedStatus = false; @ManyToOne(fetch = FetchType.LAZY) diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java index 5180f96b..f6b19cb1 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java @@ -5,6 +5,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import konkuk.chacall.domain.foodtruck.application.FoodTruckService; +import konkuk.chacall.global.common.storage.dto.ImageRequest; +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.response.FoodTruckMenuResponse; +import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckNameDuplicateCheckResponse; +import konkuk.chacall.domain.foodtruck.presentation.dto.response.FoodTruckResponse; +import konkuk.chacall.global.common.storage.dto.ImageResponse; import konkuk.chacall.domain.foodtruck.presentation.dto.request.*; import konkuk.chacall.domain.foodtruck.presentation.dto.response.*; import konkuk.chacall.global.common.annotation.ExceptionDescription; diff --git a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java index c3571214..df344d08 100644 --- a/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java +++ b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java @@ -4,6 +4,8 @@ import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; +import java.util.List; + public record SavedFoodTruckResponse( @Schema(description = "푸드트럭 식별자", example = "1") Long foodTruckId, @@ -13,6 +15,8 @@ public record SavedFoodTruckResponse( String photoUrl, @Schema(description = "푸드트럭 설명", example = "맛있는 푸드트럭입니다.") String description, + @Schema(description = "푸드트럭 음식 카테고리 (라벨 리스트)", example = "[\"한식\",\"분식\"]") + List menuCategories, @Schema(description = "푸드트럭 평균 평점", example = "4.5") Double averageRating, @Schema(description = "푸드트럭 평점 수", example = "100") @@ -25,6 +29,7 @@ public static SavedFoodTruckResponse of(FoodTruck foodTruck) { foodTruckInfo.getName(), foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) foodTruckInfo.getDescription(), + foodTruckInfo.getMenuCategoryList().getMenuCategoryLabelList(), foodTruck.getRatingInfo().getAverageRating(), foodTruck.getRatingInfo().getRatingCount() ); diff --git a/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java b/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java index aa51f1bd..4fd332ec 100644 --- a/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java +++ b/src/main/java/konkuk/chacall/domain/owner/application/OwnerService.java @@ -1,5 +1,6 @@ package konkuk.chacall.domain.owner.application; +import jakarta.validation.Valid; import konkuk.chacall.domain.owner.application.myfoodtruckmenu.MyFoodTruckMenuService; import konkuk.chacall.domain.owner.presentation.dto.request.MyFoodTruckMenuRequest; import konkuk.chacall.domain.owner.presentation.dto.response.MyFoodTruckMenuResponse; @@ -14,6 +15,10 @@ import konkuk.chacall.global.common.dto.CursorPagingResponse; import konkuk.chacall.global.common.dto.CursorPagingRequest; import konkuk.chacall.global.common.dto.SortType; +import konkuk.chacall.global.common.storage.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; +import konkuk.chacall.global.common.storage.presign.PresignedUrlService; +import konkuk.chacall.global.common.storage.util.KeyUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,6 +35,9 @@ public class OwnerService { private final OwnerReservationService ownerReservationService; private final MyFoodTruckService myFoodTruckService; private final MyFoodTruckMenuService myFoodTruckMenuService; + private final PresignedUrlService presignedUrlService; + + private static final int FOOD_TRUCK_DOCUMENT_IMAGE_MAX_COUNT = 6; // 파사드에서 사장님 검증을 거침으로써 서비스 로직에서는 사장님 검증에 신경쓰지 않도록 책임 분리 private final OwnerValidator ownerValidator; @@ -200,4 +208,16 @@ public void createNewFoodTruck(Long ownerId, FoodTruckCreateRequest request) { // 푸드트럭 최초 등록 myFoodTruckService.createNewFoodTruck(owner, request); } + + public ImageResponse createFoodTruckDocumentPresignedUrls(ImageRequest request, Long ownerId) { + // 사장님인지 먼저 검증 + User owner = ownerValidator.validateAndGetOwner(ownerId); + + return presignedUrlService.generatePresignedUrls( + request, + owner.getUserId(), + FOOD_TRUCK_DOCUMENT_IMAGE_MAX_COUNT, + KeyUtils::buildFoodTruckDocumentImageKey + ); + } } diff --git a/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java b/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java index 7630a45f..8ea34817 100644 --- a/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java +++ b/src/main/java/konkuk/chacall/domain/owner/presentation/OwnerController.java @@ -12,6 +12,8 @@ import konkuk.chacall.global.common.dto.BaseResponse; import konkuk.chacall.global.common.dto.CursorPagingRequest; import konkuk.chacall.global.common.dto.CursorPagingResponse; +import konkuk.chacall.global.common.storage.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; import konkuk.chacall.global.common.swagger.SwaggerResponseDescription; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -299,4 +301,17 @@ public BaseResponse createNewFoodTruck( ownerService.createNewFoodTruck(ownerId, request); return BaseResponse.ok(null); } + + @Operation( + summary = "푸드트럭 관련 서류 업로드를 위한 presigned URL 발급", + description = "푸드트럭 관련 서류 업로드를 위한 presigned URL을 발급받습니다." + ) + @ExceptionDescription(SwaggerResponseDescription.OWNER_GET_FOOD_TRUCK_DOCUMENT_PRESIGEND_URL) + @PostMapping("/me/food-truck-documents/images") + public BaseResponse createFoodTruckDocumentPresignedUrls( + @Valid @RequestBody final ImageRequest request, + @Parameter(hidden = true) @UserId final Long ownerId + ) { + return BaseResponse.ok(ownerService.createFoodTruckDocumentPresignedUrls(request, ownerId)); + } } diff --git a/src/main/java/konkuk/chacall/domain/reservation/application/status/ReservationStatusService.java b/src/main/java/konkuk/chacall/domain/reservation/application/status/ReservationStatusService.java index d51df29e..8a17a2db 100644 --- a/src/main/java/konkuk/chacall/domain/reservation/application/status/ReservationStatusService.java +++ b/src/main/java/konkuk/chacall/domain/reservation/application/status/ReservationStatusService.java @@ -7,7 +7,7 @@ import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.global.common.exception.EntityNotFoundException; import konkuk.chacall.global.common.exception.code.ErrorCode; -import konkuk.chacall.global.common.storage.PdfService; +import konkuk.chacall.global.common.storage.pdf.PdfService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/konkuk/chacall/domain/user/application/UserService.java b/src/main/java/konkuk/chacall/domain/user/application/UserService.java index 3e5cdb37..3e8852ed 100644 --- a/src/main/java/konkuk/chacall/domain/user/application/UserService.java +++ b/src/main/java/konkuk/chacall/domain/user/application/UserService.java @@ -1,7 +1,5 @@ package konkuk.chacall.domain.user.application; -import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; -import konkuk.chacall.domain.foodtruck.domain.repository.FoodTruckRepository; import konkuk.chacall.domain.user.application.admin.AdminService; import konkuk.chacall.domain.user.application.validator.AdminValidator; import konkuk.chacall.domain.user.presentation.dto.request.ApproveFoodTruckStatusRequest; @@ -11,6 +9,10 @@ import konkuk.chacall.domain.user.presentation.dto.response.UserResponse; import konkuk.chacall.global.common.exception.EntityNotFoundException; import konkuk.chacall.global.common.exception.code.ErrorCode; +import konkuk.chacall.global.common.storage.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; +import konkuk.chacall.global.common.storage.presign.PresignedUrlService; +import konkuk.chacall.global.common.storage.util.KeyUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +27,9 @@ public class UserService { private final AdminService adminService; private final AdminValidator adminValidator; + private final PresignedUrlService presignedUrlService; + private static final int USER_PROFILE_IMAGE_MAX_COUNT = 1; + public UserResponse getUserInfo(Long userId) { return userRepository.findById(userId) .map(UserResponse::from) @@ -45,4 +50,16 @@ public void approveFoodTruckStatus(Long userId, Long foodTruckId, ApproveFoodTru adminService.approveFoodTruckStatus(foodTruckId, request); } + + public ImageResponse createUserImagePresignedUrl(Long userId, ImageRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException(ErrorCode.USER_NOT_FOUND)); + + return presignedUrlService.generatePresignedUrls( + request, + user.getUserId(), + USER_PROFILE_IMAGE_MAX_COUNT, + KeyUtils::buildUserProfileImageKey + ); + } } diff --git a/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java index ebc67643..fedfdac3 100644 --- a/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java +++ b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java @@ -11,6 +11,8 @@ import konkuk.chacall.global.common.annotation.ExceptionDescription; import konkuk.chacall.global.common.annotation.UserId; import konkuk.chacall.global.common.dto.BaseResponse; +import konkuk.chacall.global.common.storage.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; import konkuk.chacall.global.common.swagger.SwaggerResponseDescription; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -64,4 +66,17 @@ public BaseResponse approveFoodTruckStatus ( userService.approveFoodTruckStatus(userId, foodTruckId, request); return BaseResponse.ok(null); } + + @Operation( + summary = "프로필 변경을 위한 presigned URL 발급", + description = "프로필 변경을 위한 presigned URL을 발급합니다." + ) + @ExceptionDescription(SwaggerResponseDescription.GET_USER_PROFILE_PRESIGEND_URL) + @PostMapping("/me/images") + public BaseResponse getProfilePresignedUrl( + @Valid @RequestBody final ImageRequest request, + @Parameter(hidden = true) @UserId final Long userId + ) { + return BaseResponse.ok(userService.createUserImagePresignedUrl(userId, request)); + } } diff --git a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java index 9d4285c1..4dcb4978 100644 --- a/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java +++ b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java @@ -84,6 +84,9 @@ public enum ErrorCode implements ResponseCode { NEED_ELECTRICITY_MISMATCH(HttpStatus.BAD_REQUEST, 110008, "전기 사용 여부 값이 올바르지 않습니다."), PAYMENT_METHOD_MISMATCH(HttpStatus.BAD_REQUEST, 110009, "결제 수단 값이 올바르지 않습니다."), + /** + * Image + */ INVALID_IMAGE_COUNT(HttpStatus.BAD_REQUEST, 115001, "이미지 개수가 유효하지 않습니다."), INVALID_FILE_EXTENSION(HttpStatus.BAD_REQUEST, 115002, "허용되지 않는 파일 확장자입니다."), diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/ImageRequest.java b/src/main/java/konkuk/chacall/global/common/storage/dto/ImageRequest.java similarity index 88% rename from src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/ImageRequest.java rename to src/main/java/konkuk/chacall/global/common/storage/dto/ImageRequest.java index 900fb053..fb96a337 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/request/ImageRequest.java +++ b/src/main/java/konkuk/chacall/global/common/storage/dto/ImageRequest.java @@ -1,4 +1,4 @@ -package konkuk.chacall.domain.foodtruck.presentation.dto.request; +package konkuk.chacall.global.common.storage.dto; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/ImageResponse.java b/src/main/java/konkuk/chacall/global/common/storage/dto/ImageResponse.java similarity index 92% rename from src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/ImageResponse.java rename to src/main/java/konkuk/chacall/global/common/storage/dto/ImageResponse.java index 7c101e8b..cc2aff54 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/dto/response/ImageResponse.java +++ b/src/main/java/konkuk/chacall/global/common/storage/dto/ImageResponse.java @@ -1,4 +1,4 @@ -package konkuk.chacall.domain.foodtruck.presentation.dto.response; +package konkuk.chacall.global.common.storage.dto; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/src/main/java/konkuk/chacall/global/common/storage/PdfService.java b/src/main/java/konkuk/chacall/global/common/storage/pdf/PdfService.java similarity index 94% rename from src/main/java/konkuk/chacall/global/common/storage/PdfService.java rename to src/main/java/konkuk/chacall/global/common/storage/pdf/PdfService.java index 8affd4ef..e13980e2 100644 --- a/src/main/java/konkuk/chacall/global/common/storage/PdfService.java +++ b/src/main/java/konkuk/chacall/global/common/storage/pdf/PdfService.java @@ -1,7 +1,8 @@ -package konkuk.chacall.global.common.storage; +package konkuk.chacall.global.common.storage.pdf; import konkuk.chacall.domain.reservation.domain.model.Reservation; import konkuk.chacall.global.common.annotation.HelperService; +import konkuk.chacall.global.common.storage.S3Service; import konkuk.chacall.global.common.storage.util.CdnUrlResolver; import konkuk.chacall.global.common.storage.util.PdfGenerator; import konkuk.chacall.global.common.storage.util.PdfTemplateRenderer; diff --git a/src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java b/src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java new file mode 100644 index 00000000..a309f2cf --- /dev/null +++ b/src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java @@ -0,0 +1,57 @@ +package konkuk.chacall.global.common.storage.presign; + +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.dto.ImageRequest; +import konkuk.chacall.global.common.storage.dto.ImageResponse; +import konkuk.chacall.global.common.storage.util.AllowedFileExtension; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.function.Function; + +@Service +@RequiredArgsConstructor +public class PresignedUrlService { + + private final S3Service s3Service; + + public ImageResponse generatePresignedUrls( + ImageRequest request, + Long userId, + Integer maxCountOrNull, + Function baseKeyBuilder + ) { + final List extensions = request.fileExtensions(); + + // 1) 개수 검증 + if (maxCountOrNull != null && extensions.size() > maxCountOrNull) { + throw new BusinessException( + ErrorCode.INVALID_IMAGE_COUNT, + new IllegalArgumentException("이미지 개수는 1 이상 " + maxCountOrNull + " 이하이어야 합니다. 입력: " + extensions.size()) + ); + } + + // 2) 확장자 검증 + AllowedFileExtension.checkAllowedExtension(extensions); + + // 3) 키 조합 및 Presigned URL 생성 + final String baseKey = baseKeyBuilder.apply(userId); + + var imageInfos = extensions.stream() + .map(extension -> { + final String keyWithExt = baseKey + "." + extension.toLowerCase(); + + final String presignedUrl = s3Service.generatePresignedUrl(keyWithExt); + final String fileUrl = s3Service.getFileUrl(keyWithExt); + + return ImageResponse.ImageInfo.of(presignedUrl, fileUrl); + }) + .toList(); + + return ImageResponse.of(imageInfos); + } +} \ No newline at end of file diff --git a/src/main/java/konkuk/chacall/global/common/storage/util/KeyUtils.java b/src/main/java/konkuk/chacall/global/common/storage/util/KeyUtils.java index 91c1b541..5a4cb445 100644 --- a/src/main/java/konkuk/chacall/global/common/storage/util/KeyUtils.java +++ b/src/main/java/konkuk/chacall/global/common/storage/util/KeyUtils.java @@ -30,12 +30,30 @@ public static String buildFoodTruckImageKey(Long userId) { uuid); // foodtrucks/{userId}/{timestamp}/{uuid} } - public static String buildMenuImageKey(Long foodTruckId) { + public static String buildMenuImageKey(Long userId) { String timestamp = LocalDateTime.now().format(DATE_TIME_FORMATTER); // ex) 20250923-163045 String uuid = UUID.randomUUID().toString(); return String.format("menus/%d/%s/%s", - foodTruckId, + userId, + timestamp, + uuid); // menus/{userId}/{timestamp}/{uuid} + } + + public static String buildUserProfileImageKey(Long userId) { + String timestamp = LocalDateTime.now().format(DATE_TIME_FORMATTER); // ex) 20250923-163045 + String uuid = UUID.randomUUID().toString(); + return String.format("users/%d/%s/%s", + userId, + timestamp, + uuid); // users/{userId}/{timestamp}/{uuid} + } + + public static String buildFoodTruckDocumentImageKey(Long userId) { + String timestamp = LocalDateTime.now().format(DATE_TIME_FORMATTER); // ex) 20250923-163045 + String uuid = UUID.randomUUID().toString(); + return String.format("foodtruck-documents/%d//%s/%s", + userId, timestamp, - uuid); // menus/{foodTruckId}/{timestamp}/{uuid} + uuid); // foodtruck-documents/{userId}/{timestamp}/{uuid} } } \ No newline at end of file diff --git a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java index 11ddede5..32c2b4c0 100644 --- a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java @@ -29,6 +29,11 @@ public enum SwaggerResponseDescription { UPDATE_USER_INFO(new LinkedHashSet<>(Set.of( USER_NOT_FOUND ))), + GET_USER_PROFILE_PRESIGEND_URL(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + INVALID_IMAGE_COUNT, + INVALID_FILE_EXTENSION + ))), // Owner OWNER_REGISTER_BANK_ACCOUNT(new LinkedHashSet<>(Set.of( @@ -130,6 +135,12 @@ public enum SwaggerResponseDescription { USER_NOT_FOUND, USER_FORBIDDEN ))), + OWNER_GET_FOOD_TRUCK_DOCUMENT_PRESIGEND_URL(new LinkedHashSet<>(Set.of( + USER_NOT_FOUND, + USER_FORBIDDEN, + INVALID_IMAGE_COUNT, + INVALID_FILE_EXTENSION + ))), // Member diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index eddce8bc..42ea5f82 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -10,7 +10,7 @@ spring: jpa: hibernate: - ddl-auto: create + ddl-auto: update show-sql: true properties: hibernate: