From a23f8dc994997c7c0bfce64538cd11681724c7fd Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 25 Oct 2025 19:35:55 +0900 Subject: [PATCH 1/8] =?UTF-8?q?[refactor]=20PresignedUrlService=EC=97=90?= =?UTF-8?q?=EC=84=9C=20presignedUrl=20=EA=B4=80=EB=A0=A8=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=A1=9C=EC=A7=81=20=EB=8B=B4=EB=8B=B9=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/FoodTruckService.java | 4 +- .../image/FoodTruckImageService.java | 65 +++++-------------- .../presentation/FoodTruckController.java | 6 +- .../status/ReservationStatusService.java | 2 +- .../common/exception/code/ErrorCode.java | 3 + .../common/storage/dto}/ImageRequest.java | 2 +- .../common/storage/dto}/ImageResponse.java | 2 +- .../common/storage/{ => pdf}/PdfService.java | 3 +- .../storage/presign/PresignedUrlService.java | 57 ++++++++++++++++ 9 files changed, 85 insertions(+), 59 deletions(-) rename src/main/java/konkuk/chacall/{domain/foodtruck/presentation/dto/request => global/common/storage/dto}/ImageRequest.java (88%) rename src/main/java/konkuk/chacall/{domain/foodtruck/presentation/dto/response => global/common/storage/dto}/ImageResponse.java (92%) rename src/main/java/konkuk/chacall/global/common/storage/{ => pdf}/PdfService.java (94%) create mode 100644 src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java 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 dd6203f5..6244241b 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/application/FoodTruckService.java @@ -6,11 +6,11 @@ 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.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.domain.foodtruck.presentation.dto.response.ImageResponse; +import konkuk.chacall.global.common.storage.dto.ImageResponse; import konkuk.chacall.domain.owner.application.validator.OwnerValidator; import konkuk.chacall.domain.user.domain.model.User; import konkuk.chacall.domain.member.application.validator.MemberValidator; 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 21592650..32b3cb52 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 @@ -1,68 +1,35 @@ package konkuk.chacall.domain.foodtruck.application.image; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest; -import konkuk.chacall.domain.foodtruck.presentation.dto.response.ImageResponse; +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.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.presign.PresignedUrlService; 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 PresignedUrlService presignedUrlService; private static final int MAX_FOOD_TRUCK_IMAGE_COUNT = 9; -// private static final int MAX_MENU_IMAGE_COUNT = 5; 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, + MAX_FOOD_TRUCK_IMAGE_COUNT, + (user) -> KeyUtils.buildFoodTruckImageKey(user.getUserId()) + ); } 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, + null, + (user) -> KeyUtils.buildMenuImageKey(user.getUserId()) + ); } } \ No newline at end of file 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 72fcc10c..d9fa4e9e 100644 --- a/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java +++ b/src/main/java/konkuk/chacall/domain/foodtruck/presentation/FoodTruckController.java @@ -5,16 +5,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import konkuk.chacall.domain.foodtruck.application.FoodTruckService; -import konkuk.chacall.domain.foodtruck.presentation.dto.request.ImageRequest; +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.domain.foodtruck.presentation.dto.response.ImageResponse; -import konkuk.chacall.domain.owner.presentation.dto.request.MyFoodTruckMenuRequest; -import konkuk.chacall.domain.owner.presentation.dto.response.MyFoodTruckMenuResponse; +import konkuk.chacall.global.common.storage.dto.ImageResponse; import konkuk.chacall.global.common.annotation.ExceptionDescription; import konkuk.chacall.global.common.annotation.UserId; import konkuk.chacall.global.common.dto.BaseResponse; 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/global/common/exception/code/ErrorCode.java b/src/main/java/konkuk/chacall/global/common/exception/code/ErrorCode.java index da0c349e..400b175f 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 @@ -79,6 +79,9 @@ public enum ErrorCode implements ResponseCode { FOOD_TRUCK_NOT_APPROVED(HttpStatus.FORBIDDEN, 110003, "승인된 푸드트럭이 아닙니다."), INVALID_FOOD_TRUCK_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, 110004, "허용되지 않은 푸드트럭 상태 변경입니다."), + /** + * 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..da695cb1 --- /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, + User owner, + 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(owner); + + 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 From 3f8a8a7c6347a4b0e3461c44d03eff6019631451 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 25 Oct 2025 19:45:40 +0900 Subject: [PATCH 2/8] =?UTF-8?q?[refactor]=20=EB=9E=8C=EB=8B=A4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B0=B8=EC=A1=B0=20?= =?UTF-8?q?=ED=98=95=ED=83=9C=EB=A1=9C=20=EC=88=98=EC=A0=95=20(#49)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/image/FoodTruckImageService.java | 8 ++++---- .../storage/presign/PresignedUrlService.java | 6 +++--- .../global/common/storage/util/KeyUtils.java | 15 ++++++++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) 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 32b3cb52..75a224b8 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 @@ -18,18 +18,18 @@ public class FoodTruckImageService { public ImageResponse createFoodTruckImagePresignedUrl(ImageRequest request, User owner) { return presignedUrlService.generatePresignedUrls( request, - owner, + owner.getUserId(), MAX_FOOD_TRUCK_IMAGE_COUNT, - (user) -> KeyUtils.buildFoodTruckImageKey(user.getUserId()) + KeyUtils::buildFoodTruckImageKey ); } public ImageResponse createMenuImagePresignedUrl(ImageRequest request, User owner) { return presignedUrlService.generatePresignedUrls( request, - owner, + owner.getUserId(), null, - (user) -> KeyUtils.buildMenuImageKey(user.getUserId()) + KeyUtils::buildMenuImageKey ); } } \ No newline at end of file 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 index da695cb1..a309f2cf 100644 --- a/src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java +++ b/src/main/java/konkuk/chacall/global/common/storage/presign/PresignedUrlService.java @@ -21,9 +21,9 @@ public class PresignedUrlService { public ImageResponse generatePresignedUrls( ImageRequest request, - User owner, + Long userId, Integer maxCountOrNull, - Function baseKeyBuilder + Function baseKeyBuilder ) { final List extensions = request.fileExtensions(); @@ -39,7 +39,7 @@ public ImageResponse generatePresignedUrls( AllowedFileExtension.checkAllowedExtension(extensions); // 3) 키 조합 및 Presigned URL 생성 - final String baseKey = baseKeyBuilder.apply(owner); + final String baseKey = baseKeyBuilder.apply(userId); var imageInfos = extensions.stream() .map(extension -> { 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..eeea5c72 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,21 @@ 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); // menus/{foodTruckId}/{timestamp}/{uuid} + uuid); // users/{userId}/profiles/{timestamp}/{uuid} } } \ No newline at end of file From e8d2229330673b10fd1a4140c7b9c6ef9d68a70e Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 25 Oct 2025 19:46:00 +0900 Subject: [PATCH 3/8] =?UTF-8?q?[feat]=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=9D=84=20=EC=9C=84=ED=95=9C=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20presignedUrl?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=20API=20=EA=B5=AC=ED=98=84=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/application/UserService.java | 21 +++++++++++++++++-- .../user/presentation/UserController.java | 15 +++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) 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..553bf04e 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_PROFILE_PRESIGNED_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)); + } } From 9c0e9b25636301f9677dd19eb287ea3febef19ba Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sat, 25 Oct 2025 19:51:02 +0900 Subject: [PATCH 4/8] =?UTF-8?q?[feat]=20=ED=91=B8=EB=93=9C=ED=8A=B8?= =?UTF-8?q?=EB=9F=AD=20=EA=B4=80=EB=A0=A8=20=EC=84=9C=EB=A5=98=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=EB=A5=BC=20=EC=9C=84=ED=95=9C=20presignedUrl?= =?UTF-8?q?=20=EB=B0=9C=EA=B8=89=20API=20=EA=B5=AC=ED=98=84=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../owner/application/OwnerService.java | 20 +++++++++++++++++++ .../owner/presentation/OwnerController.java | 15 ++++++++++++++ .../user/presentation/UserController.java | 2 +- .../global/common/storage/util/KeyUtils.java | 11 +++++++++- .../swagger/SwaggerResponseDescription.java | 5 +++++ 5 files changed, 51 insertions(+), 2 deletions(-) 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/user/presentation/UserController.java b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java index 553bf04e..fedfdac3 100644 --- a/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java +++ b/src/main/java/konkuk/chacall/domain/user/presentation/UserController.java @@ -71,7 +71,7 @@ public BaseResponse approveFoodTruckStatus ( summary = "프로필 변경을 위한 presigned URL 발급", description = "프로필 변경을 위한 presigned URL을 발급합니다." ) - @ExceptionDescription(SwaggerResponseDescription.GET_PROFILE_PRESIGNED_URL) + @ExceptionDescription(SwaggerResponseDescription.GET_USER_PROFILE_PRESIGEND_URL) @PostMapping("/me/images") public BaseResponse getProfilePresignedUrl( @Valid @RequestBody final ImageRequest request, 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 eeea5c72..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 @@ -45,6 +45,15 @@ public static String buildUserProfileImageKey(Long userId) { return String.format("users/%d/%s/%s", userId, timestamp, - uuid); // users/{userId}/profiles/{timestamp}/{uuid} + 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); // 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 e2069c5f..b0bd8e81 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( From 1470a2ddf5e7b8c8c85224135f66f6bf050e9a3f Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 26 Oct 2025 00:48:48 +0900 Subject: [PATCH 5/8] =?UTF-8?q?[refactor]=20=EC=A0=80=EC=9E=A5=ED=95=9C=20?= =?UTF-8?q?=ED=91=B8=EB=93=9C=ED=8A=B8=EB=9F=AD=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EC=97=90=EC=84=9C=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=98=ED=99=98=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/dto/response/SavedFoodTruckResponse.java | 5 +++++ 1 file changed, 5 insertions(+) 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 ca76ddfb..cd831237 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 @@ -3,6 +3,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import java.util.List; + public record SavedFoodTruckResponse( @Schema(description = "푸드트럭 식별자", example = "1") Long foodTruckId, @@ -12,6 +14,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") @@ -23,6 +27,7 @@ public static SavedFoodTruckResponse of(FoodTruck foodTruck) { foodTruck.getName(), foodTruck.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) foodTruck.getDescription(), + foodTruck.getMenuCategoryList().getMenuCategoryLabelList(), foodTruck.getRatingInfo().getAverageRating(), foodTruck.getRatingInfo().getRatingCount() ); From 4edc8e81fdc40e147d51d7bf049eff9633508ee2 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Sun, 26 Oct 2025 01:04:02 +0900 Subject: [PATCH 6/8] =?UTF-8?q?[docs]=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=20?= =?UTF-8?q?=EB=AA=85=EC=84=B8=20=EC=B6=94=EA=B0=80=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/common/swagger/SwaggerResponseDescription.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 b0bd8e81..82eda58f 100644 --- a/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java +++ b/src/main/java/konkuk/chacall/global/common/swagger/SwaggerResponseDescription.java @@ -134,6 +134,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 From 948dd84cff49f2c058cce44aa8cfc6652cf9c15f Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Tue, 28 Oct 2025 14:16:35 +0900 Subject: [PATCH 7/8] =?UTF-8?q?[merge]=20merge=20=EB=B0=98=EC=98=81=20(#49?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/FoodTruckImageService.java | 22 +++++++++++++++++++ .../foodtruck/domain/model/FoodTruck.java | 4 ++-- .../dto/response/SavedFoodTruckResponse.java | 3 ++- 3 files changed, 26 insertions(+), 3 deletions(-) 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 75a224b8..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 @@ -1,9 +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.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.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; @@ -15,6 +22,10 @@ public class FoodTruckImageService { private final PresignedUrlService presignedUrlService; private static final int MAX_FOOD_TRUCK_IMAGE_COUNT = 9; + private final FoodTruckRepository foodTruckRepository; + private final S3Service s3Service; + private final CdnUrlResolver cdnUrlResolver; + public ImageResponse createFoodTruckImagePresignedUrl(ImageRequest request, User owner) { return presignedUrlService.generatePresignedUrls( request, @@ -32,4 +43,15 @@ public ImageResponse createMenuImagePresignedUrl(ImageRequest request, User owne KeyUtils::buildMenuImageKey ); } + + 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); + } } \ No newline at end of file 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/member/presentation/dto/response/SavedFoodTruckResponse.java b/src/main/java/konkuk/chacall/domain/member/presentation/dto/response/SavedFoodTruckResponse.java index f64d5c1d..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 @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import konkuk.chacall.domain.foodtruck.domain.model.FoodTruck; +import konkuk.chacall.domain.foodtruck.domain.value.FoodTruckInfo; import java.util.List; @@ -28,7 +29,7 @@ public static SavedFoodTruckResponse of(FoodTruck foodTruck) { foodTruckInfo.getName(), foodTruckInfo.getFoodTruckPhotoList().getMainPhotoUrl(), // 대표 사진 (첫 번째 사진) foodTruckInfo.getDescription(), - foodTruck.getMenuCategoryList().getMenuCategoryLabelList(), + foodTruckInfo.getMenuCategoryList().getMenuCategoryLabelList(), foodTruck.getRatingInfo().getAverageRating(), foodTruck.getRatingInfo().getRatingCount() ); From 39fc1fb67ad754206394935a84313a1f1adf1c28 Mon Sep 17 00:00:00 2001 From: janghyunjun Date: Wed, 29 Oct 2025 17:39:26 +0900 Subject: [PATCH 8/8] =?UTF-8?q?[chore]=20ddl-auto=20=EC=88=98=EC=A0=95=20(?= =?UTF-8?q?#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: