From 552fb6e582776c362009447b2d58204851ba218d Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 21 Aug 2024 22:18:41 +0900 Subject: [PATCH 01/64] KL-119/feat: add s3 config --- build.gradle | 4 ++ .../taco/klkl/global/config/S3/S3Config.java | 48 +++++++++++++++++++ src/main/resources/application-storage.yaml | 18 +++++++ src/main/resources/application.yaml | 1 + 4 files changed, 71 insertions(+) create mode 100644 src/main/java/taco/klkl/global/config/S3/S3Config.java create mode 100644 src/main/resources/application-storage.yaml diff --git a/build.gradle b/build.gradle index 97a9fcc7..b4a30d08 100644 --- a/build.gradle +++ b/build.gradle @@ -56,6 +56,10 @@ dependencies { // MySQL runtimeOnly 'com.mysql:mysql-connector-j' + + // S3 + implementation platform('software.amazon.awssdk:bom:2.20.56') + implementation 'software.amazon.awssdk:s3' } checkstyle { diff --git a/src/main/java/taco/klkl/global/config/S3/S3Config.java b/src/main/java/taco/klkl/global/config/S3/S3Config.java new file mode 100644 index 00000000..13d799ae --- /dev/null +++ b/src/main/java/taco/klkl/global/config/S3/S3Config.java @@ -0,0 +1,48 @@ +package taco.klkl.global.config.S3; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.access-key}") + private String accessKey; + + @Value("${cloud.aws.credentials.secret-key}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AwsCredentialsProvider awsCredentialsProvider() { + AwsCredentials awsCredentials = AwsBasicCredentials.create(accessKey, secretKey); + return StaticCredentialsProvider.create(awsCredentials); + } + + @Bean + public S3Client s3Client() { + return S3Client.builder() + .region(Region.of(region)) + .credentialsProvider(awsCredentialsProvider()) + .build(); + } + + @Bean + public S3Presigner s3Presigner() { + return S3Presigner.builder() + .region(Region.of(region)) + .credentialsProvider(awsCredentialsProvider()) + .build(); + } +} diff --git a/src/main/resources/application-storage.yaml b/src/main/resources/application-storage.yaml new file mode 100644 index 00000000..dc895894 --- /dev/null +++ b/src/main/resources/application-storage.yaml @@ -0,0 +1,18 @@ +spring: + config: + activate: + on-profile: "storage" + servlet: + multipart: + max-file-size: 5MB + max-request-size: 10MB + resolve-lazily: true +cloud: + aws: + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + region: + static: ${S3_REGION} + s3: + bucket: ${S3_BUCKET_NAME} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e070fbd6..4a7027f7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -8,6 +8,7 @@ spring: dev: "dev, mysql" include: - swagger + - storage application: name: klkl From 15d13415de26c32734f4a3fa828abe770fb629e7 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 21 Aug 2024 22:19:43 +0900 Subject: [PATCH 02/64] KL-119/feat: add FileExtension enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 파일 확장자 유효하지 않은 경우 던지는 FileExtensionInvalidException도 추가 --- .../domain/image/domain/FileExtension.java | 25 +++++++++++++++++++ .../FileExtensionInvalidException.java | 10 ++++++++ .../global/error/exception/ErrorCode.java | 3 +++ 3 files changed, 38 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/domain/FileExtension.java create mode 100644 src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java diff --git a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java new file mode 100644 index 00000000..b1c4291a --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java @@ -0,0 +1,25 @@ +package taco.klkl.domain.image.domain; + +import java.util.Arrays; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.image.exception.FileExtensionInvalidException; + +@Getter +@RequiredArgsConstructor +public enum FileExtension { + JPEG("jpeg"), + JPG("jpg"), + PNG("png"), + ; + + private final String value; + + public static FileExtension from(final String value) throws FileExtensionInvalidException { + return Arrays.stream(FileExtension.values()) + .filter(extension -> extension.getValue().equals(value)) + .findFirst() + .orElseThrow(FileExtensionInvalidException::new); + } +} diff --git a/src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java b/src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java new file mode 100644 index 00000000..c2e88c95 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class FileExtensionInvalidException extends CustomException { + public FileExtensionInvalidException() { + super(ErrorCode.FILE_EXTENSION_INVALID); + } +} diff --git a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java index 961aeb3e..a0c0c256 100644 --- a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java +++ b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java @@ -57,6 +57,9 @@ public enum ErrorCode { // Search + // Image + FILE_EXTENSION_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 파일 확장자입니다."), + // Sample SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "샘플 에러입니다."), ; From 272da0c8f38666e0d55d04c1592cc8727580e1c0 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Thu, 22 Aug 2024 20:01:35 +0900 Subject: [PATCH 03/64] KL-119/feat: add Image entity --- .../taco/klkl/domain/image/domain/Image.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/domain/Image.java diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java new file mode 100644 index 00000000..ac42dfcc --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -0,0 +1,44 @@ +package taco.klkl.domain.image.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity(name = "image") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Image { + @Id + @Column(name = "image_id") + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Column(name = "image_uuid") + private String imageUuid; + + @Enumerated(EnumType.STRING) + @Column(name = "file_extension") + private FileExtension fileExtension; + + private Image( + final String imageUuid, + final FileExtension fileExtension + ) { + this.imageUuid = imageUuid; + this.fileExtension = fileExtension; + } + + public static Image of( + final String imageUuid, + final FileExtension fileExtension + ) { + return new Image(imageUuid, fileExtension); + } +} From ac6c8f32bfae89124892c5e37524eb4d125faf60 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Thu, 22 Aug 2024 20:01:51 +0900 Subject: [PATCH 04/64] KL-119/feat: add ImageCreateRequest --- .../domain/image/dto/request/ImageCreateRequest.java | 11 +++++++++++ .../common/constants/ImageValidationMessages.java | 7 +++++++ 2 files changed, 18 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java create mode 100644 src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java diff --git a/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java new file mode 100644 index 00000000..4969b870 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java @@ -0,0 +1,11 @@ +package taco.klkl.domain.image.dto.request; + +import jakarta.validation.constraints.NotNull; +import taco.klkl.domain.image.domain.FileExtension; +import taco.klkl.global.common.constants.ImageValidationMessages; + +public record ImageCreateRequest( + @NotNull(message = ImageValidationMessages.IMAGE_FILE_EXTENSION_NOT_NULL) + FileExtension fileExtension +) { +} diff --git a/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java new file mode 100644 index 00000000..95d3fba0 --- /dev/null +++ b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java @@ -0,0 +1,7 @@ +package taco.klkl.global.common.constants; + +public final class ImageValidationMessages { + public static final String IMAGE_FILE_EXTENSION_NOT_NULL = "이미지 파일의 확장자는 필수 항목입니다."; + + private ImageValidationMessages() {} +} From 0f43f1b69862eb0a653767a17c77359e72f321b2 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Thu, 22 Aug 2024 20:02:03 +0900 Subject: [PATCH 05/64] KL-119/feat: add PresignedUrlResponse --- .../domain/image/dto/response/PresignedUrlResponse.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/dto/response/PresignedUrlResponse.java diff --git a/src/main/java/taco/klkl/domain/image/dto/response/PresignedUrlResponse.java b/src/main/java/taco/klkl/domain/image/dto/response/PresignedUrlResponse.java new file mode 100644 index 00000000..4d883ea7 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dto/response/PresignedUrlResponse.java @@ -0,0 +1,9 @@ +package taco.klkl.domain.image.dto.response; + +public record PresignedUrlResponse( + String presignedUrl +) { + public static PresignedUrlResponse from(final String presignedUrl) { + return new PresignedUrlResponse(presignedUrl); + } +} From 3ad89d9172eae3c457c6447eb2eaffe699f4e59c Mon Sep 17 00:00:00 2001 From: ohhamma Date: Thu, 22 Aug 2024 20:02:48 +0900 Subject: [PATCH 06/64] KL-119/feat: add ImageRepository --- .../java/taco/klkl/domain/image/dao/ImageRepository.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/dao/ImageRepository.java diff --git a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java new file mode 100644 index 00000000..ac1e39f4 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java @@ -0,0 +1,8 @@ +package taco.klkl.domain.image.dao; + +import org.springframework.data.jpa.repository.JpaRepository; + +import taco.klkl.domain.image.domain.Image; + +public interface ImageRepository extends JpaRepository { +} From 30ef446819466b313fe7d974b01d4cdcb988faa2 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Thu, 22 Aug 2024 20:03:07 +0900 Subject: [PATCH 07/64] KL-119/feat: create image presigned url --- .../image/controller/ImageController.java | 30 +++++++ .../domain/image/service/ImageService.java | 12 +++ .../image/service/ImageServiceImpl.java | 84 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/controller/ImageController.java create mode 100644 src/main/java/taco/klkl/domain/image/service/ImageService.java create mode 100644 src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java new file mode 100644 index 00000000..06c6619c --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -0,0 +1,30 @@ +package taco.klkl.domain.image.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.dto.response.PresignedUrlResponse; +import taco.klkl.domain.image.service.ImageService; + +@RestController +@Tag(name = "10. 이미지", description = "이미지 관련 API") +@RequiredArgsConstructor +public class ImageController { + + public final ImageService imageService; + + @Operation(summary = "이미지 Presigned URL 생성", description = "이미지 Presigned URL를 생성합니다.") + @PostMapping("/v1/images/upload-url") + public PresignedUrlResponse createImagePresignedUrl( + @Valid @RequestBody ImageCreateRequest request + ) { + return imageService.createImagePresignedUrl(request); + } +} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java new file mode 100644 index 00000000..c90af14d --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -0,0 +1,12 @@ +package taco.klkl.domain.image.service; + +import org.springframework.stereotype.Service; + +import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.dto.response.PresignedUrlResponse; + +@Service +public interface ImageService { + + PresignedUrlResponse createImagePresignedUrl(final ImageCreateRequest createRequest); +} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java new file mode 100644 index 00000000..90e90a2e --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -0,0 +1,84 @@ +package taco.klkl.domain.image.service; + +import java.time.Duration; +import java.util.UUID; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.ObjectCannedACL; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; +import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest; +import taco.klkl.domain.image.dao.ImageRepository; +import taco.klkl.domain.image.domain.FileExtension; +import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.dto.response.PresignedUrlResponse; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ImageServiceImpl implements ImageService { + + private final S3Client s3Client; + private final S3Presigner s3Presigner; + + private final ImageRepository imageRepository; + + @Value("${cloud.aws.s3.bucket}") + private String bucketName; + + @Override + public PresignedUrlResponse createImagePresignedUrl(final ImageCreateRequest createRequest) { + final String imageUUID = generateUUID(); + final String fileName = createFileName(imageUUID, createRequest.fileExtension()); + + final PutObjectRequest objectRequest = PutObjectRequest.builder() + .bucket(bucketName) + .key(fileName) + .contentType("image/" + createRequest.fileExtension().getValue()) + .acl(ObjectCannedACL.PUBLIC_READ) + .build(); + + final PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder() + .signatureDuration(Duration.ofMinutes(30)) + .putObjectRequest(objectRequest) + .build(); + + final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(presignRequest); + final String presignedUrl = presignedRequest.url().toString(); + + final Image image = createImageEntity(imageUUID, createRequest.fileExtension()); + imageRepository.save(image); + + System.out.println("Generated Presigned URL: " + presignedUrl); + System.out.println("Bucket: " + bucketName); + System.out.println("Key: " + fileName); + System.out.println("Content-Type: image/" + createRequest.fileExtension().getValue()); + return PresignedUrlResponse.from(presignedUrl); + } + + private String generateUUID() { + return UUID.randomUUID().toString(); + } + + private String createFileName( + String imageUUID, + FileExtension fileExtension + ) { + StringBuilder sb = new StringBuilder(); + sb.append(imageUUID) + .append(".") + .append(fileExtension.getValue()); + return sb.toString(); + } + + private Image createImageEntity(final String imageUUID, final FileExtension fileExtension) { + return Image.of(imageUUID, fileExtension); + } +} From cf9e717b5ce63cb900e1a5e394cbc457e4a68392 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Fri, 23 Aug 2024 13:24:06 +0900 Subject: [PATCH 08/64] docs: rename invalid controller tags --- .../taco/klkl/domain/category/controller/TagController.java | 2 +- .../taco/klkl/domain/search/controller/SearchController.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/taco/klkl/domain/category/controller/TagController.java b/src/main/java/taco/klkl/domain/category/controller/TagController.java index f747b1cf..075ae221 100644 --- a/src/main/java/taco/klkl/domain/category/controller/TagController.java +++ b/src/main/java/taco/klkl/domain/category/controller/TagController.java @@ -18,7 +18,7 @@ import taco.klkl.domain.category.service.SubcategoryTagService; @Slf4j -@Tag(name = "7. 태그", description = "태그 관련 API") +@Tag(name = "6. 카테고리", description = "카테고리 관련 API") @RestController @RequestMapping("/v1/tags") @RequiredArgsConstructor diff --git a/src/main/java/taco/klkl/domain/search/controller/SearchController.java b/src/main/java/taco/klkl/domain/search/controller/SearchController.java index f9115164..de824d9c 100644 --- a/src/main/java/taco/klkl/domain/search/controller/SearchController.java +++ b/src/main/java/taco/klkl/domain/search/controller/SearchController.java @@ -17,7 +17,7 @@ @RestController @RequestMapping("/v1/search") @RequiredArgsConstructor -@Tag(name = "6. 검색", description = "검색 관련 API") +@Tag(name = "8. 검색", description = "검색 관련 API") public class SearchController { private final SearchService searchService; From 2e7ae973bd7ae578db7e3591235fb7b0cc028550 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Fri, 23 Aug 2024 14:46:45 +0900 Subject: [PATCH 09/64] KL-119/refactor: KL-119/feat: add imageType and targetId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이미지 업로드 서비스를 유저 프로필 이미지 업로드 서비스로 변경 - ImageType enum 추가 및 Image 엔티티 필드에 추가 - targetId도 Image 엔티티 필드에 추가 --- .../image/controller/ImageController.java | 16 +-- .../domain/image/domain/FileExtension.java | 8 +- .../taco/klkl/domain/image/domain/Image.java | 45 +++++++- .../klkl/domain/image/domain/ImageType.java | 24 +++++ .../image/dto/request/ImageCreateRequest.java | 11 -- .../dto/request/UserProfileUploadRequest.java | 10 ++ .../FileExtensionNotFoundException.java | 10 ++ ...n.java => ImageTypeNotFoundException.java} | 6 +- .../domain/image/service/ImageService.java | 4 +- .../image/service/ImageServiceImpl.java | 100 +++++++++++++----- .../constants/ImageValidationMessages.java | 2 +- .../global/error/exception/ErrorCode.java | 3 +- 12 files changed, 178 insertions(+), 61 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/image/domain/ImageType.java delete mode 100644 src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java create mode 100644 src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java create mode 100644 src/main/java/taco/klkl/domain/image/exception/FileExtensionNotFoundException.java rename src/main/java/taco/klkl/domain/image/exception/{FileExtensionInvalidException.java => ImageTypeNotFoundException.java} (50%) diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index 06c6619c..961ed8a3 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -2,14 +2,13 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.service.ImageService; @@ -20,11 +19,14 @@ public class ImageController { public final ImageService imageService; - @Operation(summary = "이미지 Presigned URL 생성", description = "이미지 Presigned URL를 생성합니다.") - @PostMapping("/v1/images/upload-url") - public PresignedUrlResponse createImagePresignedUrl( - @Valid @RequestBody ImageCreateRequest request + @Operation( + summary = "유저 프로필 이미지 업로드용 Presigned URL 생성", + description = "유저 프로필 이미지 업로드용 Presigned URL를 생성합니다." + ) + @PostMapping("/v1/users/me/upload-url") + public PresignedUrlResponse createUserProfileUploadPresignedUrl( + @Valid @RequestBody UserProfileUploadRequest request ) { - return imageService.createImagePresignedUrl(request); + return imageService.createUserProfileUploadPresignedUrl(request); } } diff --git a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java index b1c4291a..7522ec8d 100644 --- a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java +++ b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java @@ -4,7 +4,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import taco.klkl.domain.image.exception.FileExtensionInvalidException; +import taco.klkl.domain.image.exception.FileExtensionNotFoundException; @Getter @RequiredArgsConstructor @@ -16,10 +16,10 @@ public enum FileExtension { private final String value; - public static FileExtension from(final String value) throws FileExtensionInvalidException { + public static FileExtension from(final String fileExtension) throws FileExtensionNotFoundException { return Arrays.stream(FileExtension.values()) - .filter(extension -> extension.getValue().equals(value)) + .filter(extension -> extension.toString().equals(fileExtension)) .findFirst() - .orElseThrow(FileExtensionInvalidException::new); + .orElseThrow(FileExtensionNotFoundException::new); } } diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java index ac42dfcc..a9742491 100644 --- a/src/main/java/taco/klkl/domain/image/domain/Image.java +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -1,5 +1,7 @@ package taco.klkl.domain.image.domain; +import java.time.LocalDateTime; + import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -16,29 +18,64 @@ @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Image { @Id - @Column(name = "image_id") + @Column(name = "image_id", + nullable = false + ) @GeneratedValue(strategy = GenerationType.AUTO) private Long id; - @Column(name = "image_uuid") + @Enumerated(EnumType.STRING) + @Column( + name = "image_type", + nullable = false + ) + private ImageType imageType; + + @Column( + name = "target_id", + nullable = false + ) + private Long targetId; + + @Column( + name = "image_uuid", + nullable = false + ) private String imageUuid; @Enumerated(EnumType.STRING) - @Column(name = "file_extension") + @Column( + name = "file_extension", + nullable = false + ) private FileExtension fileExtension; + @Column( + name = "created_at", + nullable = false, + updatable = false + ) + private LocalDateTime createdAt; + private Image( + final ImageType imageType, + final Long targetId, final String imageUuid, final FileExtension fileExtension ) { + this.imageType = imageType; + this.targetId = targetId; this.imageUuid = imageUuid; this.fileExtension = fileExtension; + this.createdAt = LocalDateTime.now(); } public static Image of( + final ImageType imageType, + final Long targetId, final String imageUuid, final FileExtension fileExtension ) { - return new Image(imageUuid, fileExtension); + return new Image(imageType, targetId, imageUuid, fileExtension); } } diff --git a/src/main/java/taco/klkl/domain/image/domain/ImageType.java b/src/main/java/taco/klkl/domain/image/domain/ImageType.java new file mode 100644 index 00000000..254b127d --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/domain/ImageType.java @@ -0,0 +1,24 @@ +package taco.klkl.domain.image.domain; + +import java.util.Arrays; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.image.exception.ImageTypeNotFoundException; + +@Getter +@RequiredArgsConstructor +public enum ImageType { + USER_PROFILE("user_profile"), + PRODUCT_IMAGE("product_image"), + ; + + private final String value; + + public static ImageType from(final String value) { + return Arrays.stream(ImageType.values()) + .filter(type -> type.getValue().equals(value)) + .findFirst() + .orElseThrow(ImageTypeNotFoundException::new); + } +} diff --git a/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java deleted file mode 100644 index 4969b870..00000000 --- a/src/main/java/taco/klkl/domain/image/dto/request/ImageCreateRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package taco.klkl.domain.image.dto.request; - -import jakarta.validation.constraints.NotNull; -import taco.klkl.domain.image.domain.FileExtension; -import taco.klkl.global.common.constants.ImageValidationMessages; - -public record ImageCreateRequest( - @NotNull(message = ImageValidationMessages.IMAGE_FILE_EXTENSION_NOT_NULL) - FileExtension fileExtension -) { -} diff --git a/src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java new file mode 100644 index 00000000..42b94e5b --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.dto.request; + +import jakarta.validation.constraints.NotBlank; +import taco.klkl.global.common.constants.ImageValidationMessages; + +public record UserProfileUploadRequest( + @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) + String fileExtension +) { +} diff --git a/src/main/java/taco/klkl/domain/image/exception/FileExtensionNotFoundException.java b/src/main/java/taco/klkl/domain/image/exception/FileExtensionNotFoundException.java new file mode 100644 index 00000000..cec97ec1 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/FileExtensionNotFoundException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class FileExtensionNotFoundException extends CustomException { + public FileExtensionNotFoundException() { + super(ErrorCode.FILE_EXTENSION_NOT_FOUND); + } +} diff --git a/src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java b/src/main/java/taco/klkl/domain/image/exception/ImageTypeNotFoundException.java similarity index 50% rename from src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java rename to src/main/java/taco/klkl/domain/image/exception/ImageTypeNotFoundException.java index c2e88c95..a0324d97 100644 --- a/src/main/java/taco/klkl/domain/image/exception/FileExtensionInvalidException.java +++ b/src/main/java/taco/klkl/domain/image/exception/ImageTypeNotFoundException.java @@ -3,8 +3,8 @@ import taco.klkl.global.error.exception.CustomException; import taco.klkl.global.error.exception.ErrorCode; -public class FileExtensionInvalidException extends CustomException { - public FileExtensionInvalidException() { - super(ErrorCode.FILE_EXTENSION_INVALID); +public class ImageTypeNotFoundException extends CustomException { + public ImageTypeNotFoundException() { + super(ErrorCode.IMAGE_TYPE_NOT_FOUND); } } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index c90af14d..cd6d94f2 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -2,11 +2,11 @@ import org.springframework.stereotype.Service; -import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @Service public interface ImageService { - PresignedUrlResponse createImagePresignedUrl(final ImageCreateRequest createRequest); + PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfileUploadRequest createRequest); } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 90e90a2e..fbcb2b46 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -4,7 +4,9 @@ import java.util.UUID; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,49 +19,62 @@ import taco.klkl.domain.image.dao.ImageRepository; import taco.klkl.domain.image.domain.FileExtension; import taco.klkl.domain.image.domain.Image; -import taco.klkl.domain.image.dto.request.ImageCreateRequest; +import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; +import taco.klkl.domain.user.domain.User; +import taco.klkl.global.util.UserUtil; @Slf4j +@Primary @Service +@Transactional(readOnly = true) @RequiredArgsConstructor public class ImageServiceImpl implements ImageService { + private static final Duration SIGNATURE_DURATION = Duration.ofMinutes(5); + private static final ObjectCannedACL REQUEST_ACL = ObjectCannedACL.PRIVATE; + private final S3Client s3Client; private final S3Presigner s3Presigner; private final ImageRepository imageRepository; + private final UserUtil userUtil; + @Value("${cloud.aws.s3.bucket}") private String bucketName; @Override - public PresignedUrlResponse createImagePresignedUrl(final ImageCreateRequest createRequest) { + @Transactional + public PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfileUploadRequest createRequest) { + final User currentUser = userUtil.findCurrentUser(); final String imageUUID = generateUUID(); - final String fileName = createFileName(imageUUID, createRequest.fileExtension()); - - final PutObjectRequest objectRequest = PutObjectRequest.builder() - .bucket(bucketName) - .key(fileName) - .contentType("image/" + createRequest.fileExtension().getValue()) - .acl(ObjectCannedACL.PUBLIC_READ) - .build(); - - final PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder() - .signatureDuration(Duration.ofMinutes(30)) - .putObjectRequest(objectRequest) - .build(); - - final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(presignRequest); + final FileExtension fileExtension = FileExtension.from(createRequest.fileExtension()); + final String fileName = createFileName( + ImageType.USER_PROFILE, + currentUser.getId(), + imageUUID, + fileExtension + ); + + final PutObjectRequest putObjectRequest = createPutObjectRequest( + fileName, + fileExtension + ); + final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); + + final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); final String presignedUrl = presignedRequest.url().toString(); - final Image image = createImageEntity(imageUUID, createRequest.fileExtension()); + final Image image = createImageEntity( + ImageType.USER_PROFILE, + currentUser.getId(), + imageUUID, + fileExtension + ); imageRepository.save(image); - System.out.println("Generated Presigned URL: " + presignedUrl); - System.out.println("Bucket: " + bucketName); - System.out.println("Key: " + fileName); - System.out.println("Content-Type: image/" + createRequest.fileExtension().getValue()); return PresignedUrlResponse.from(presignedUrl); } @@ -68,17 +83,46 @@ private String generateUUID() { } private String createFileName( - String imageUUID, - FileExtension fileExtension + final ImageType imageType, + final Long targetId, + final String imageUUID, + final FileExtension fileExtension ) { StringBuilder sb = new StringBuilder(); - sb.append(imageUUID) - .append(".") + sb.append(imageType.getValue()).append("/") + .append(targetId).append("/") + .append(imageUUID).append(".") .append(fileExtension.getValue()); return sb.toString(); } - private Image createImageEntity(final String imageUUID, final FileExtension fileExtension) { - return Image.of(imageUUID, fileExtension); + private PutObjectRequest createPutObjectRequest( + final String fileName, + final FileExtension fileExtension + ) { + return PutObjectRequest.builder() + .bucket(bucketName) + .key(fileName) + .contentType("image/" + fileExtension.getValue()) + .acl(REQUEST_ACL) + .build(); + } + + private PutObjectPresignRequest createPutObjectPresignRequest( + final PutObjectRequest putObjectRequest + ) { + return PutObjectPresignRequest.builder() + .signatureDuration(SIGNATURE_DURATION) + .putObjectRequest(putObjectRequest) + .build(); + } + + private Image createImageEntity( + final ImageType imageType, + final Long targetId, + final String imageUUID, + final FileExtension fileExtension + ) { + return Image.of(imageType, targetId, imageUUID, fileExtension); } } diff --git a/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java index 95d3fba0..e36b79af 100644 --- a/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java +++ b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java @@ -1,7 +1,7 @@ package taco.klkl.global.common.constants; public final class ImageValidationMessages { - public static final String IMAGE_FILE_EXTENSION_NOT_NULL = "이미지 파일의 확장자는 필수 항목입니다."; + public static final String FILE_EXTENSION_NOT_BLANK = "파일 확장자는 비어있을 수 없습니다."; private ImageValidationMessages() {} } diff --git a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java index a0c0c256..5e417993 100644 --- a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java +++ b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java @@ -58,7 +58,8 @@ public enum ErrorCode { // Search // Image - FILE_EXTENSION_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 파일 확장자입니다."), + FILE_EXTENSION_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 파일 확장자입니다."), + IMAGE_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 이미지 타입입니다."), // Sample SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "샘플 에러입니다."), From 17ca8f9412e91467605171a383ae45299eb8c359 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Fri, 23 Aug 2024 18:22:40 +0900 Subject: [PATCH 10/64] KL-119/refactor: separate profile image upload method --- .../image/controller/ImageController.java | 32 ------------------- .../taco/klkl/domain/image/domain/Image.java | 19 +++-------- ...ava => UserProfileImageUploadRequest.java} | 2 +- .../domain/image/service/ImageService.java | 4 +-- .../image/service/ImageServiceImpl.java | 28 ++++++++-------- .../user/controller/UserController.java | 25 ++++++++++++--- .../constants/ImageValidationMessages.java | 3 +- .../global/config/{S3 => s3}/S3Config.java | 2 +- 8 files changed, 45 insertions(+), 70 deletions(-) delete mode 100644 src/main/java/taco/klkl/domain/image/controller/ImageController.java rename src/main/java/taco/klkl/domain/image/dto/request/{UserProfileUploadRequest.java => UserProfileImageUploadRequest.java} (85%) rename src/main/java/taco/klkl/global/config/{S3 => s3}/S3Config.java (97%) diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java deleted file mode 100644 index 961ed8a3..00000000 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ /dev/null @@ -1,32 +0,0 @@ -package taco.klkl.domain.image.controller; - -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; -import taco.klkl.domain.image.dto.response.PresignedUrlResponse; -import taco.klkl.domain.image.service.ImageService; - -@RestController -@Tag(name = "10. 이미지", description = "이미지 관련 API") -@RequiredArgsConstructor -public class ImageController { - - public final ImageService imageService; - - @Operation( - summary = "유저 프로필 이미지 업로드용 Presigned URL 생성", - description = "유저 프로필 이미지 업로드용 Presigned URL를 생성합니다." - ) - @PostMapping("/v1/users/me/upload-url") - public PresignedUrlResponse createUserProfileUploadPresignedUrl( - @Valid @RequestBody UserProfileUploadRequest request - ) { - return imageService.createUserProfileUploadPresignedUrl(request); - } -} diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java index a9742491..e8bfa576 100644 --- a/src/main/java/taco/klkl/domain/image/domain/Image.java +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -32,16 +32,10 @@ public class Image { private ImageType imageType; @Column( - name = "target_id", + name = "image_key", nullable = false ) - private Long targetId; - - @Column( - name = "image_uuid", - nullable = false - ) - private String imageUuid; + private String imageKey; @Enumerated(EnumType.STRING) @Column( @@ -59,23 +53,20 @@ public class Image { private Image( final ImageType imageType, - final Long targetId, - final String imageUuid, + final String imageKey, final FileExtension fileExtension ) { this.imageType = imageType; - this.targetId = targetId; - this.imageUuid = imageUuid; + this.imageKey = imageKey; this.fileExtension = fileExtension; this.createdAt = LocalDateTime.now(); } public static Image of( final ImageType imageType, - final Long targetId, final String imageUuid, final FileExtension fileExtension ) { - return new Image(imageType, targetId, imageUuid, fileExtension); + return new Image(imageType, imageUuid, fileExtension); } } diff --git a/src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java similarity index 85% rename from src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java rename to src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java index 42b94e5b..3db08c21 100644 --- a/src/main/java/taco/klkl/domain/image/dto/request/UserProfileUploadRequest.java +++ b/src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.NotBlank; import taco.klkl.global.common.constants.ImageValidationMessages; -public record UserProfileUploadRequest( +public record UserProfileImageUploadRequest( @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) String fileExtension ) { diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index cd6d94f2..1df9cb49 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -2,11 +2,11 @@ import org.springframework.stereotype.Service; -import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; +import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @Service public interface ImageService { - PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfileUploadRequest createRequest); + PresignedUrlResponse generateProfileImageUploadUrl(final UserProfileImageUploadRequest createRequest); } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index fbcb2b46..e2b929c2 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -20,7 +20,7 @@ import taco.klkl.domain.image.domain.FileExtension; import taco.klkl.domain.image.domain.Image; import taco.klkl.domain.image.domain.ImageType; -import taco.klkl.domain.image.dto.request.UserProfileUploadRequest; +import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.user.domain.User; import taco.klkl.global.util.UserUtil; @@ -47,14 +47,14 @@ public class ImageServiceImpl implements ImageService { @Override @Transactional - public PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfileUploadRequest createRequest) { + public PresignedUrlResponse generateProfileImageUploadUrl(final UserProfileImageUploadRequest uploadRequest) { final User currentUser = userUtil.findCurrentUser(); - final String imageUUID = generateUUID(); - final FileExtension fileExtension = FileExtension.from(createRequest.fileExtension()); + final String imageKey = generateImageKey(); + final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); final String fileName = createFileName( ImageType.USER_PROFILE, currentUser.getId(), - imageUUID, + imageKey, fileExtension ); @@ -69,8 +69,7 @@ public PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfil final Image image = createImageEntity( ImageType.USER_PROFILE, - currentUser.getId(), - imageUUID, + imageKey, fileExtension ); imageRepository.save(image); @@ -78,20 +77,20 @@ public PresignedUrlResponse createUserProfileUploadPresignedUrl(final UserProfil return PresignedUrlResponse.from(presignedUrl); } - private String generateUUID() { + private String generateImageKey() { return UUID.randomUUID().toString(); } private String createFileName( final ImageType imageType, - final Long targetId, - final String imageUUID, + final Long currentUserId, + final String imageKey, final FileExtension fileExtension ) { StringBuilder sb = new StringBuilder(); sb.append(imageType.getValue()).append("/") - .append(targetId).append("/") - .append(imageUUID).append(".") + .append(currentUserId).append("/") + .append(imageKey).append(".") .append(fileExtension.getValue()); return sb.toString(); } @@ -119,10 +118,9 @@ private PutObjectPresignRequest createPutObjectPresignRequest( private Image createImageEntity( final ImageType imageType, - final Long targetId, - final String imageUUID, + final String imageKey, final FileExtension fileExtension ) { - return Image.of(imageType, targetId, imageUUID, fileExtension); + return Image.of(imageType, imageKey, fileExtension); } } diff --git a/src/main/java/taco/klkl/domain/user/controller/UserController.java b/src/main/java/taco/klkl/domain/user/controller/UserController.java index d61bbcd3..5972e0e2 100644 --- a/src/main/java/taco/klkl/domain/user/controller/UserController.java +++ b/src/main/java/taco/klkl/domain/user/controller/UserController.java @@ -2,12 +2,19 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; +import taco.klkl.domain.image.dto.response.PresignedUrlResponse; +import taco.klkl.domain.image.service.ImageService; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.domain.user.service.UserService; @@ -15,12 +22,11 @@ @RestController @Tag(name = "1. 유저", description = "유저 관련 API") @RequestMapping("/v1/users") +@RequiredArgsConstructor public class UserController { - final UserService userService; - public UserController(UserService userService) { - this.userService = userService; - } + private final UserService userService; + private final ImageService imageService; @Operation(summary = "내 정보 조회", description = "내 정보를 조회합니다. (테스트용)") @GetMapping("/me") @@ -28,4 +34,15 @@ public ResponseEntity getMe() { UserDetailResponse userDto = userService.getMyInfo(); return ResponseEntity.ok().body(userDto); } + + @Operation( + summary = "프로필 이미지 업로드를 위한 Presigned URL 생성", + description = "프로필 이미지 업로드를 위한 Presigned URL를 생성합니다." + ) + @PostMapping("/me/profile-image/upload-url") + public PresignedUrlResponse createProfileImageUploadUrl( + @Valid @RequestBody UserProfileImageUploadRequest request + ) { + return imageService.generateProfileImageUploadUrl(request); + } } diff --git a/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java index e36b79af..8927161f 100644 --- a/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java +++ b/src/main/java/taco/klkl/global/common/constants/ImageValidationMessages.java @@ -3,5 +3,6 @@ public final class ImageValidationMessages { public static final String FILE_EXTENSION_NOT_BLANK = "파일 확장자는 비어있을 수 없습니다."; - private ImageValidationMessages() {} + private ImageValidationMessages() { + } } diff --git a/src/main/java/taco/klkl/global/config/S3/S3Config.java b/src/main/java/taco/klkl/global/config/s3/S3Config.java similarity index 97% rename from src/main/java/taco/klkl/global/config/S3/S3Config.java rename to src/main/java/taco/klkl/global/config/s3/S3Config.java index 13d799ae..c6cb0dde 100644 --- a/src/main/java/taco/klkl/global/config/S3/S3Config.java +++ b/src/main/java/taco/klkl/global/config/s3/S3Config.java @@ -1,4 +1,4 @@ -package taco.klkl.global.config.S3; +package taco.klkl.global.config.s3; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; From 2b13870038052ae2817eaec3ce9cb0314c61b694 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Mon, 26 Aug 2024 16:59:28 +0900 Subject: [PATCH 11/64] feat: add FileExtension.WEBP --- src/main/java/taco/klkl/domain/image/domain/FileExtension.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java index 7522ec8d..f420c9a4 100644 --- a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java +++ b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java @@ -12,6 +12,7 @@ public enum FileExtension { JPEG("jpeg"), JPG("jpg"), PNG("png"), + WEBP("webp"), ; private final String value; From 75a5323f6a2a5d668f988621a1103d852aa658d1 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Mon, 26 Aug 2024 17:00:18 +0900 Subject: [PATCH 12/64] feat: add cloudfront domain --- src/main/resources/application-storage.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application-storage.yaml b/src/main/resources/application-storage.yaml index dc895894..2294fde8 100644 --- a/src/main/resources/application-storage.yaml +++ b/src/main/resources/application-storage.yaml @@ -15,4 +15,6 @@ cloud: region: static: ${S3_REGION} s3: - bucket: ${S3_BUCKET_NAME} \ No newline at end of file + bucket: ${S3_BUCKET_NAME} + cloudfront: + domain: ${CLOUDFRONT_DOMAIN} \ No newline at end of file From 98f465f37f6da0042ec4e555a4e9a3d007df80ac Mon Sep 17 00:00:00 2001 From: ohhamma Date: Mon, 26 Aug 2024 17:01:16 +0900 Subject: [PATCH 13/64] feat: add UploadState --- .../klkl/domain/image/domain/UploadState.java | 24 +++++++++++++++++++ .../UploadStateNotFoundException.java | 10 ++++++++ .../global/error/exception/ErrorCode.java | 4 +++- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 src/main/java/taco/klkl/domain/image/domain/UploadState.java create mode 100644 src/main/java/taco/klkl/domain/image/exception/UploadStateNotFoundException.java diff --git a/src/main/java/taco/klkl/domain/image/domain/UploadState.java b/src/main/java/taco/klkl/domain/image/domain/UploadState.java new file mode 100644 index 00000000..9763b8c2 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/domain/UploadState.java @@ -0,0 +1,24 @@ +package taco.klkl.domain.image.domain; + +import java.util.Arrays; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.image.exception.UploadStateNotFoundException; + +@Getter +@RequiredArgsConstructor +public enum UploadState { + PENDING("대기중"), + COMPLETE("완료"), + ; + + private final String value; + + public static UploadState from(final String value) throws UploadStateNotFoundException { + return Arrays.stream(UploadState.values()) + .filter(state -> state.getValue().equals(value)) + .findFirst() + .orElseThrow(UploadStateNotFoundException::new); + } +} diff --git a/src/main/java/taco/klkl/domain/image/exception/UploadStateNotFoundException.java b/src/main/java/taco/klkl/domain/image/exception/UploadStateNotFoundException.java new file mode 100644 index 00000000..8f7b9792 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/UploadStateNotFoundException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class UploadStateNotFoundException extends CustomException { + public UploadStateNotFoundException() { + super(ErrorCode.UPLOAD_STATE_NOT_FOUND); + } +} diff --git a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java index 5e417993..e2aea3fc 100644 --- a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java +++ b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java @@ -59,7 +59,9 @@ public enum ErrorCode { // Image FILE_EXTENSION_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 파일 확장자입니다."), - IMAGE_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 이미지 타입입니다."), + IMAGE_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 이미지 타입입니다."), + UPLOAD_STATE_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 업로드 상태입니다."), + USER_IMAGE_NOT_FOUND(HttpStatus.FOUND, "존재하지 않는 유저 이미지입니다."), // Sample SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "샘플 에러입니다."), From dda770043a6f809d837030e30c81e28fd66e2698 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Mon, 26 Aug 2024 17:03:40 +0900 Subject: [PATCH 14/64] refactor: move refactor: move presigned url api to image domain --- .../image/controller/ImageController.java | 34 +++++++++++++++++++ .../user/controller/UserController.java | 18 ---------- 2 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/image/controller/ImageController.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java new file mode 100644 index 00000000..d2e010c4 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -0,0 +1,34 @@ +package taco.klkl.domain.image.controller; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.response.PresignedUrlResponse; +import taco.klkl.domain.image.service.ImageService; + +@Slf4j +@RestController +@Tag(name = "0. 이미지", description = "이미지 관련 API") +@RequiredArgsConstructor +public class ImageController { + + private final ImageService imageService; + + @Operation( + summary = "유저 이미지 업로드를 위한 Presigned URL 생성", + description = "유저 이미지 업로드를 위한 Presigned URL를 생성합니다." + ) + @PostMapping("/me/image/upload-url") + public PresignedUrlResponse createUserImageUploadUrl( + @Valid @RequestBody UserImageUploadRequest request + ) { + return imageService.createUserImageUploadUrl(request); + } +} diff --git a/src/main/java/taco/klkl/domain/user/controller/UserController.java b/src/main/java/taco/klkl/domain/user/controller/UserController.java index 5972e0e2..104695fb 100644 --- a/src/main/java/taco/klkl/domain/user/controller/UserController.java +++ b/src/main/java/taco/klkl/domain/user/controller/UserController.java @@ -2,19 +2,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; -import taco.klkl.domain.image.dto.response.PresignedUrlResponse; -import taco.klkl.domain.image.service.ImageService; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.domain.user.service.UserService; @@ -26,7 +20,6 @@ public class UserController { private final UserService userService; - private final ImageService imageService; @Operation(summary = "내 정보 조회", description = "내 정보를 조회합니다. (테스트용)") @GetMapping("/me") @@ -34,15 +27,4 @@ public ResponseEntity getMe() { UserDetailResponse userDto = userService.getMyInfo(); return ResponseEntity.ok().body(userDto); } - - @Operation( - summary = "프로필 이미지 업로드를 위한 Presigned URL 생성", - description = "프로필 이미지 업로드를 위한 Presigned URL를 생성합니다." - ) - @PostMapping("/me/profile-image/upload-url") - public PresignedUrlResponse createProfileImageUploadUrl( - @Valid @RequestBody UserProfileImageUploadRequest request - ) { - return imageService.generateProfileImageUploadUrl(request); - } } From 12cfbfa4ee8950289e20ad0eb32098b10ca48089 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 15:55:17 +0900 Subject: [PATCH 15/64] KL-119/fix: FileExtension JPG value to jpeg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MIME type 규정에 맞게 jpeg로 수정 --- src/main/java/taco/klkl/domain/image/domain/FileExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java index f420c9a4..c1d2c441 100644 --- a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java +++ b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java @@ -9,8 +9,8 @@ @Getter @RequiredArgsConstructor public enum FileExtension { + JPG("jpeg"), JPEG("jpeg"), - JPG("jpg"), PNG("png"), WEBP("webp"), ; From 7ff3d538af8f6f68a6d1983c9f2159b6cdce851e Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 15:57:48 +0900 Subject: [PATCH 16/64] KL-119/refactor: change Gender static method logic --- .../java/taco/klkl/domain/user/domain/Gender.java | 11 +++++------ .../user/exception/GenderNotFoundException.java | 10 ++++++++++ .../taco/klkl/global/error/exception/ErrorCode.java | 6 +++++- 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/user/exception/GenderNotFoundException.java diff --git a/src/main/java/taco/klkl/domain/user/domain/Gender.java b/src/main/java/taco/klkl/domain/user/domain/Gender.java index 02d86861..cc8d9298 100644 --- a/src/main/java/taco/klkl/domain/user/domain/Gender.java +++ b/src/main/java/taco/klkl/domain/user/domain/Gender.java @@ -4,22 +4,21 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import taco.klkl.domain.user.exception.GenderNotFoundException; @Getter @AllArgsConstructor public enum Gender { MALE("남"), FEMALE("여"), - NONE(""), ; - private final String description; + private final String value; - public static Gender getGenderByDescription(String description) { + public static Gender from(final String value) { return Arrays.stream(Gender.values()) - .filter(g -> g.getDescription().equals(description)) + .filter(gender -> gender.getValue().equals(value)) .findFirst() - .orElse(NONE); + .orElseThrow(GenderNotFoundException::new); } - } diff --git a/src/main/java/taco/klkl/domain/user/exception/GenderNotFoundException.java b/src/main/java/taco/klkl/domain/user/exception/GenderNotFoundException.java new file mode 100644 index 00000000..d3ef7b65 --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/exception/GenderNotFoundException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.user.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class GenderNotFoundException extends CustomException { + public GenderNotFoundException() { + super(ErrorCode.GENDER_NOT_FOUND); + } +} diff --git a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java index e2aea3fc..aac9f011 100644 --- a/src/main/java/taco/klkl/global/error/exception/ErrorCode.java +++ b/src/main/java/taco/klkl/global/error/exception/ErrorCode.java @@ -18,6 +18,8 @@ public enum ErrorCode { QUERY_PARAM_NOT_FOUND(HttpStatus.BAD_REQUEST, "쿼리 파라미터가 존재하지 않습니다."), // User + USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다."), + GENDER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 성별입니다."), // Product PRODUCT_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 상품입니다."), @@ -61,7 +63,9 @@ public enum ErrorCode { FILE_EXTENSION_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 파일 확장자입니다."), IMAGE_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 이미지 타입입니다."), UPLOAD_STATE_NOT_FOUND(HttpStatus.NOT_FOUND, "유효하지 않은 업로드 상태입니다."), - USER_IMAGE_NOT_FOUND(HttpStatus.FOUND, "존재하지 않는 유저 이미지입니다."), + IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 이미지입니다."), + IMAGE_UPLOAD_NOT_COMPLETE(HttpStatus.BAD_REQUEST, "이미지 업로드가 완료되지 않았습니다."), + IMAGE_URL_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 이미지 url 형식입니다."), // Sample SAMPLE_ERROR(HttpStatus.BAD_REQUEST, "샘플 에러입니다."), From e6f8a8aa22e4689993bd506257853a62e69dd52c Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 15:59:32 +0900 Subject: [PATCH 17/64] KL-119/refactor: change user ImageType --- src/main/java/taco/klkl/domain/image/domain/ImageType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/ImageType.java b/src/main/java/taco/klkl/domain/image/domain/ImageType.java index 254b127d..c6144f41 100644 --- a/src/main/java/taco/klkl/domain/image/domain/ImageType.java +++ b/src/main/java/taco/klkl/domain/image/domain/ImageType.java @@ -9,13 +9,13 @@ @Getter @RequiredArgsConstructor public enum ImageType { - USER_PROFILE("user_profile"), + USER_IMAGE("user_image"), PRODUCT_IMAGE("product_image"), ; private final String value; - public static ImageType from(final String value) { + public static ImageType from(final String value) throws ImageTypeNotFoundException { return Arrays.stream(ImageType.values()) .filter(type -> type.getValue().equals(value)) .findFirst() From defa912786b8643cef037c9be20e7b6985ca62d0 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:00:10 +0900 Subject: [PATCH 18/64] KL-119/feat: add targetId --- .../taco/klkl/domain/image/domain/Image.java | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java index e8bfa576..6cc0b663 100644 --- a/src/main/java/taco/klkl/domain/image/domain/Image.java +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -17,6 +17,7 @@ @Entity(name = "image") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Image { + @Id @Column(name = "image_id", nullable = false @@ -31,6 +32,12 @@ public class Image { ) private ImageType imageType; + @Column( + name = "target_id", + nullable = false + ) + private Long targetId; + @Column( name = "image_key", nullable = false @@ -44,6 +51,13 @@ public class Image { ) private FileExtension fileExtension; + @Enumerated(EnumType.STRING) + @Column( + name = "upload_state", + nullable = false + ) + private UploadState uploadState; + @Column( name = "created_at", nullable = false, @@ -53,20 +67,35 @@ public class Image { private Image( final ImageType imageType, + final Long targetId, final String imageKey, final FileExtension fileExtension ) { this.imageType = imageType; + this.targetId = targetId; this.imageKey = imageKey; this.fileExtension = fileExtension; + this.uploadState = UploadState.PENDING; this.createdAt = LocalDateTime.now(); } public static Image of( final ImageType imageType, + final Long targetId, final String imageUuid, final FileExtension fileExtension ) { - return new Image(imageType, imageUuid, fileExtension); + return new Image(imageType, targetId, imageUuid, fileExtension); + } + + public void uploadComplete() { + this.uploadState = UploadState.COMPLETE; + } + + public String createFileName() { + return imageType.getValue() + "/" + + targetId + "/" + + imageKey + "." + + fileExtension.getValue(); } } From 0536b037ea82eb659f2679192d06d523e1ba7f67 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:00:26 +0900 Subject: [PATCH 19/64] KL-119/feat: add ImageKeyGenerator --- .../klkl/domain/image/service/ImageKeyGenerator.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/service/ImageKeyGenerator.java diff --git a/src/main/java/taco/klkl/domain/image/service/ImageKeyGenerator.java b/src/main/java/taco/klkl/domain/image/service/ImageKeyGenerator.java new file mode 100644 index 00000000..1432d2a8 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/service/ImageKeyGenerator.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.service; + +import java.util.UUID; + +public final class ImageKeyGenerator { + + public static String generate() { + return UUID.randomUUID().toString(); + } +} From 0593b9c3c414f5b55ca5e0eb1fbedd57a3815536 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:01:14 +0900 Subject: [PATCH 20/64] kL-119/feat: user image upload complete --- .../image/controller/ImageController.java | 14 ++++- .../domain/image/dao/ImageRepository.java | 5 ++ .../image/dto/response/ImageUrlResponse.java | 9 +++ .../exception/ImageNotFoundException.java | 10 +++ .../domain/image/service/ImageService.java | 7 ++- .../image/service/ImageServiceImpl.java | 63 ++++++++++--------- 6 files changed, 73 insertions(+), 35 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java create mode 100644 src/main/java/taco/klkl/domain/image/exception/ImageNotFoundException.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index d2e010c4..ec3c64e4 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -10,6 +10,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.service.ImageService; @@ -22,13 +23,22 @@ public class ImageController { private final ImageService imageService; @Operation( - summary = "유저 이미지 업로드를 위한 Presigned URL 생성", + summary = "유저 이미지 업로드 Presigned URL 생성", description = "유저 이미지 업로드를 위한 Presigned URL를 생성합니다." ) - @PostMapping("/me/image/upload-url") + @PostMapping("/v1/users/me/upload-url") public PresignedUrlResponse createUserImageUploadUrl( @Valid @RequestBody UserImageUploadRequest request ) { return imageService.createUserImageUploadUrl(request); } + + @Operation( + summary = "유저 이미지 업로드 완료 처리", + description = "유저 이미지 업로드를 완료 처리합니다." + ) + @PostMapping("/v1/users/me/upload-complete") + public ImageUrlResponse uploadCompleteUserImage() { + return imageService.uploadCompleteUserImage(); + } } diff --git a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java index ac1e39f4..9933fb07 100644 --- a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java +++ b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java @@ -1,8 +1,13 @@ package taco.klkl.domain.image.dao; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.domain.ImageType; public interface ImageRepository extends JpaRepository { + Optional findByImageTypeAndTargetId(final ImageType imageType, final Long targetId); + Optional findByImageTypeAndTargetIdAndImageKey(final ImageType imageType, final Long targetId, final String imageKey); } diff --git a/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java b/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java new file mode 100644 index 00000000..8aec439e --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java @@ -0,0 +1,9 @@ +package taco.klkl.domain.image.dto.response; + +public record ImageUrlResponse( + String imageUrl +) { + public static ImageUrlResponse from(final String imageUrl) { + return new ImageUrlResponse(imageUrl); + } +} diff --git a/src/main/java/taco/klkl/domain/image/exception/ImageNotFoundException.java b/src/main/java/taco/klkl/domain/image/exception/ImageNotFoundException.java new file mode 100644 index 00000000..e82b3f66 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/ImageNotFoundException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class ImageNotFoundException extends CustomException { + public ImageNotFoundException() { + super(ErrorCode.IMAGE_NOT_FOUND); + } +} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index 1df9cb49..6ce81d5f 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -2,11 +2,14 @@ import org.springframework.stereotype.Service; -import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; +import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @Service public interface ImageService { - PresignedUrlResponse generateProfileImageUploadUrl(final UserProfileImageUploadRequest createRequest); + PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest createRequest); + + ImageUrlResponse uploadCompleteUserImage(); } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index e2b929c2..b91b4d43 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -1,7 +1,6 @@ package taco.klkl.domain.image.service; import java.time.Duration; -import java.util.UUID; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; @@ -20,8 +19,10 @@ import taco.klkl.domain.image.domain.FileExtension; import taco.klkl.domain.image.domain.Image; import taco.klkl.domain.image.domain.ImageType; -import taco.klkl.domain.image.dto.request.UserProfileImageUploadRequest; +import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; +import taco.klkl.domain.image.exception.ImageNotFoundException; import taco.klkl.domain.user.domain.User; import taco.klkl.global.util.UserUtil; @@ -45,54 +46,49 @@ public class ImageServiceImpl implements ImageService { @Value("${cloud.aws.s3.bucket}") private String bucketName; + @Value("${cloud.aws.cloudfront.domain}") + private String cloudFrontDomain; + @Override @Transactional - public PresignedUrlResponse generateProfileImageUploadUrl(final UserProfileImageUploadRequest uploadRequest) { + public PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest uploadRequest) { + final ImageType imageType = ImageType.USER_IMAGE; final User currentUser = userUtil.findCurrentUser(); - final String imageKey = generateImageKey(); + final String imageKey = ImageKeyGenerator.generate(); final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); - final String fileName = createFileName( - ImageType.USER_PROFILE, + + final Image image = createImageEntity( + imageType, currentUser.getId(), imageKey, fileExtension ); + imageRepository.save(image); final PutObjectRequest putObjectRequest = createPutObjectRequest( - fileName, - fileExtension + image.createFileName(), + image.getFileExtension() ); final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); final String presignedUrl = presignedRequest.url().toString(); - final Image image = createImageEntity( - ImageType.USER_PROFILE, - imageKey, - fileExtension - ); - imageRepository.save(image); - return PresignedUrlResponse.from(presignedUrl); } - private String generateImageKey() { - return UUID.randomUUID().toString(); - } + @Override + @Transactional + public ImageUrlResponse uploadCompleteUserImage() { + final ImageType imageType = ImageType.USER_IMAGE; + final User currentUser = userUtil.findCurrentUser(); - private String createFileName( - final ImageType imageType, - final Long currentUserId, - final String imageKey, - final FileExtension fileExtension - ) { - StringBuilder sb = new StringBuilder(); - sb.append(imageType.getValue()).append("/") - .append(currentUserId).append("/") - .append(imageKey).append(".") - .append(fileExtension.getValue()); - return sb.toString(); + final Image image = imageRepository.findByImageTypeAndTargetId(imageType, currentUser.getId()) + .orElseThrow(ImageNotFoundException::new); + + image.uploadComplete(); + final String imageUrl = createImageUrl(image); + return ImageUrlResponse.from(imageUrl); } private PutObjectRequest createPutObjectRequest( @@ -118,9 +114,14 @@ private PutObjectPresignRequest createPutObjectPresignRequest( private Image createImageEntity( final ImageType imageType, + final Long targetId, final String imageKey, final FileExtension fileExtension ) { - return Image.of(imageType, imageKey, fileExtension); + return Image.of(imageType, targetId, imageKey, fileExtension); + } + + private String createImageUrl(final Image image) { + return "https://" + cloudFrontDomain + "/" + image.createFileName(); } } From 0b9d2f9f6ef77b5633206e7f1f79f2b4db8efe3a Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:02:21 +0900 Subject: [PATCH 21/64] KL-119/fix: convert gender with static method --- .../domain/user/converter/GenderConverter.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/taco/klkl/domain/user/converter/GenderConverter.java b/src/main/java/taco/klkl/domain/user/converter/GenderConverter.java index 53dd84ee..d99b0775 100644 --- a/src/main/java/taco/klkl/domain/user/converter/GenderConverter.java +++ b/src/main/java/taco/klkl/domain/user/converter/GenderConverter.java @@ -8,24 +8,18 @@ public class GenderConverter implements AttributeConverter { @Override - public String convertToDatabaseColumn(Gender gender) { + public String convertToDatabaseColumn(final Gender gender) { if (gender == null) { return null; } - - return gender.getDescription(); + return gender.getValue(); } @Override - public Gender convertToEntityAttribute(String dbData) { + public Gender convertToEntityAttribute(final String dbData) { if (dbData == null || dbData.isEmpty()) { - throw new IllegalArgumentException("Unknown value" + dbData); + return null; } - - return switch (dbData) { - case "남" -> Gender.MALE; - case "여" -> Gender.FEMALE; - default -> throw new IllegalArgumentException("Unknown value" + dbData); - }; + return Gender.from(dbData); } } From f655ea25fefe42cb97fb11bf91bcbb68e2d7f3d4 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:03:07 +0900 Subject: [PATCH 22/64] KL-119/feat: find image by image url --- .../exception/ImageUrlInvalidException.java | 10 +++ .../java/taco/klkl/global/util/ImageUtil.java | 84 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/image/exception/ImageUrlInvalidException.java create mode 100644 src/main/java/taco/klkl/global/util/ImageUtil.java diff --git a/src/main/java/taco/klkl/domain/image/exception/ImageUrlInvalidException.java b/src/main/java/taco/klkl/domain/image/exception/ImageUrlInvalidException.java new file mode 100644 index 00000000..f047b0d2 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/ImageUrlInvalidException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class ImageUrlInvalidException extends CustomException { + public ImageUrlInvalidException() { + super(ErrorCode.IMAGE_URL_INVALID); + } +} diff --git a/src/main/java/taco/klkl/global/util/ImageUtil.java b/src/main/java/taco/klkl/global/util/ImageUtil.java new file mode 100644 index 00000000..0845568c --- /dev/null +++ b/src/main/java/taco/klkl/global/util/ImageUtil.java @@ -0,0 +1,84 @@ +package taco.klkl.global.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; +import taco.klkl.domain.image.dao.ImageRepository; +import taco.klkl.domain.image.domain.FileExtension; +import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.exception.ImageNotFoundException; +import taco.klkl.domain.image.exception.ImageUrlInvalidException; + +@Component +@RequiredArgsConstructor +public class ImageUtil { + + private static final Pattern URL_PATTERN = Pattern.compile("https://([^/]+)/([^/]+)/(\\d+)/([^/]+)\\.(\\w+)"); + + private final ImageRepository imageRepository; + + @Value("${cloud.aws.cloudfront.domain}") + private String cloudFrontDomain; + + public Image findImageByImageUrl(final ImageType imageType, final String imageUrl) { + final Matcher matcher = parseUrl(imageUrl); + validateUrlFormat(matcher); + validateUrlComponents(matcher, imageType); + + final String userId = matcher.group(3); + final String imageKey = matcher.group(4); + final String fileExtension = matcher.group(5); + + final Long parsedUserId = parseUserId(userId); + final Image image = findImageByUrlComponents(imageType, parsedUserId, imageKey); + validateFileExtension(image.getFileExtension(), fileExtension); + + return image; + } + + private Image findImageByUrlComponents(final ImageType imageType, final Long userId, final String imageKey) { + return imageRepository.findByImageTypeAndTargetIdAndImageKey(imageType, userId, imageKey) + .orElseThrow(ImageNotFoundException::new); + } + + private Matcher parseUrl(final String imageUrl) { + return URL_PATTERN.matcher(imageUrl); + } + + private Long parseUserId(final String userId) { + try { + return Long.parseLong(userId); + } catch (NumberFormatException e) { + throw new ImageUrlInvalidException(); + } + } + + private void validateUrlFormat(final Matcher matcher) { + if (!matcher.matches()) { + throw new ImageUrlInvalidException(); + } + } + + private void validateUrlComponents(final Matcher matcher, final ImageType expectedImageType) { + final String domain = matcher.group(1); + final String imageType = matcher.group(2); + + if (!domain.equals(cloudFrontDomain)) { + throw new ImageUrlInvalidException(); + } + if (!expectedImageType.getValue().equals(imageType)) { + throw new ImageUrlInvalidException(); + } + } + + private void validateFileExtension(final FileExtension imageFileExtension, final String urlFileExtension) { + if (!imageFileExtension.getValue().equals(urlFileExtension)) { + throw new ImageUrlInvalidException(); + } + } +} \ No newline at end of file From 617b93c323a74b8da6b77b1ecc29ad17f67a65db Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:04:16 +0900 Subject: [PATCH 23/64] KL-119/refactor: add profileImageUrl --- .../taco/klkl/domain/user/domain/User.java | 75 +++++++++++++++---- .../user/dto/request/UserCreateRequest.java | 3 +- .../user/dto/response/UserDetailResponse.java | 6 +- src/main/resources/database/data.sql | 2 +- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/src/main/java/taco/klkl/domain/user/domain/User.java b/src/main/java/taco/klkl/domain/user/domain/User.java index 5987828c..f6d681ad 100644 --- a/src/main/java/taco/klkl/domain/user/domain/User.java +++ b/src/main/java/taco/klkl/domain/user/domain/User.java @@ -10,7 +10,6 @@ import jakarta.persistence.PrePersist; import lombok.Getter; import lombok.NoArgsConstructor; -import taco.klkl.global.common.constants.UserConstants; @Getter @NoArgsConstructor @@ -22,42 +21,88 @@ public class User { @Column(name = "user_id") private Long id; - @Column(length = 500, nullable = false) - private String profile = UserConstants.DEFAULT_PROFILE; + @Column( + name = "profile_image_url", + length= 500, + nullable = false + ) + private String profileImageUrl; - @Column(unique = true, length = 30, nullable = false) + @Column( + name = "name", + unique = true, + length = 30, + nullable = false + ) private String name; - @Column(length = 1, nullable = false) + @Column( + name = "gender", + length = 1, + nullable = false + ) private Gender gender; - @Column(nullable = false) + @Column( + name = "age", + nullable = false + ) private Integer age; - @Column(length = 100) + @Column( + name = "description", + length = 100 + ) private String description; // TODO: created_at 이름으로 json나가야 함 - @Column(name = "created_at", nullable = false, updatable = false) + @Column( + name = "created_at", + nullable = false, + updatable = false + ) private LocalDateTime createdAt; @PrePersist protected void prePersist() { - if (this.profile == null) { - this.profile = UserConstants.DEFAULT_PROFILE; - } this.createdAt = LocalDateTime.now(); } - private User(String profile, String name, Gender gender, Integer age, String description) { - this.profile = profile; + private User( + final String profileImageUrl, + final String name, + final Gender gender, + final Integer age, + final String description + ) { + this.profileImageUrl = profileImageUrl; this.name = name; this.gender = gender; this.age = age; this.description = description; } - public static User of(String profile, String name, Gender gender, Integer age, String description) { - return new User(profile, name, gender, age, description); + public static User of( + final String profileImageUrl, + final String name, + final Gender gender, + final Integer age, + final String description + ) { + return new User(profileImageUrl, name, gender, age, description); + } + + public void update( + final String profileImageUrl, + final String name, + final Gender gender, + final Integer age, + final String description + ) { + this.profileImageUrl = profileImageUrl; + this.name = name; + this.gender = gender; + this.age = age; + this.description = description; } } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java index f77df8bd..4ec83b9e 100644 --- a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java @@ -1,5 +1,6 @@ package taco.klkl.domain.user.dto.request; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @@ -7,7 +8,7 @@ public record UserCreateRequest( @NotNull(message = "이름은 필수 항목입니다.") String name, @NotNull(message = "성별은 필수 항목입니다.") String gender, @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, - String profile, + @NotBlank(message = "프로필 사진 url은 비워둘 수 없습니다.") String profileImageUrl, String description ) { } diff --git a/src/main/java/taco/klkl/domain/user/dto/response/UserDetailResponse.java b/src/main/java/taco/klkl/domain/user/dto/response/UserDetailResponse.java index 52948eab..925907dc 100644 --- a/src/main/java/taco/klkl/domain/user/dto/response/UserDetailResponse.java +++ b/src/main/java/taco/klkl/domain/user/dto/response/UserDetailResponse.java @@ -4,15 +4,15 @@ public record UserDetailResponse( Long id, - String profile, + String profileImageUrl, String name, String description, int totalLikeCount ) { - public static UserDetailResponse from(User user) { + public static UserDetailResponse from(final User user) { return new UserDetailResponse( user.getId(), - user.getProfile(), + user.getProfileImageUrl(), user.getName(), user.getDescription(), 0 diff --git a/src/main/resources/database/data.sql b/src/main/resources/database/data.sql index 60819d87..7f18bf14 100644 --- a/src/main/resources/database/data.sql +++ b/src/main/resources/database/data.sql @@ -1,5 +1,5 @@ /* User */ -INSERT INTO klkl_user(user_id, profile, name, gender, age, description, created_at) +INSERT INTO klkl_user(user_id, profile_image_url, name, gender, age, description, created_at) VALUES (1, 'image/test.jpg', 'testUser', '남', 20, '테스트입니다.', now()); /* Like */ From e99e83fd858add9981230c3c96201b793b8e116f Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:05:06 +0900 Subject: [PATCH 24/64] KL-119/rename: change user image upload request name --- ...ofileImageUploadRequest.java => UserImageUploadRequest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/main/java/taco/klkl/domain/image/dto/request/{UserProfileImageUploadRequest.java => UserImageUploadRequest.java} (85%) diff --git a/src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java similarity index 85% rename from src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java rename to src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java index 3db08c21..213a1ca7 100644 --- a/src/main/java/taco/klkl/domain/image/dto/request/UserProfileImageUploadRequest.java +++ b/src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.NotBlank; import taco.klkl.global.common.constants.ImageValidationMessages; -public record UserProfileImageUploadRequest( +public record UserImageUploadRequest( @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) String fileExtension ) { From da147471df58aefae1900e17108ee3f3eeed7349 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:05:40 +0900 Subject: [PATCH 25/64] KL-119/fix: find test user by id 1 --- src/main/java/taco/klkl/global/util/UserUtil.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/taco/klkl/global/util/UserUtil.java b/src/main/java/taco/klkl/global/util/UserUtil.java index 946f9947..70c0ecd3 100644 --- a/src/main/java/taco/klkl/global/util/UserUtil.java +++ b/src/main/java/taco/klkl/global/util/UserUtil.java @@ -5,7 +5,7 @@ import lombok.RequiredArgsConstructor; import taco.klkl.domain.user.dao.UserRepository; import taco.klkl.domain.user.domain.User; -import taco.klkl.global.common.constants.UserConstants; +import taco.klkl.domain.user.exception.UserNotFoundException; @Component @RequiredArgsConstructor @@ -14,7 +14,8 @@ public class UserUtil { private final UserRepository userRepository; public User findTestUser() { - return userRepository.findFirstByName(UserConstants.TEST_USER_NAME); + return userRepository.findById(1L) + .orElseThrow(UserNotFoundException::new); } /** @@ -23,6 +24,6 @@ public User findTestUser() { * @return */ public User findCurrentUser() { - return userRepository.findFirstByName(UserConstants.TEST_USER_NAME); + return findTestUser(); } } From 032105ea7d0d1831a8321d376751b6fad80e9aaf Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:06:10 +0900 Subject: [PATCH 26/64] KL-119/fix: change to field profile image url --- .../klkl/domain/user/dto/response/UserSimpleResponse.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/taco/klkl/domain/user/dto/response/UserSimpleResponse.java b/src/main/java/taco/klkl/domain/user/dto/response/UserSimpleResponse.java index 9867174f..66cdb01d 100644 --- a/src/main/java/taco/klkl/domain/user/dto/response/UserSimpleResponse.java +++ b/src/main/java/taco/klkl/domain/user/dto/response/UserSimpleResponse.java @@ -4,13 +4,13 @@ public record UserSimpleResponse( Long id, - String profile, + String profileImageUrl, String name ) { - public static UserSimpleResponse from(User user) { + public static UserSimpleResponse from(final User user) { return new UserSimpleResponse( user.getId(), - user.getProfile(), + user.getProfileImageUrl(), user.getName() ); } From 8b450e558388c3cd7ed3c4922a98802f443aefd7 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:06:47 +0900 Subject: [PATCH 27/64] KL-119/feat: add UserNotFoundException --- .../domain/user/exception/UserNotFoundException.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/taco/klkl/domain/user/exception/UserNotFoundException.java diff --git a/src/main/java/taco/klkl/domain/user/exception/UserNotFoundException.java b/src/main/java/taco/klkl/domain/user/exception/UserNotFoundException.java new file mode 100644 index 00000000..10401bcc --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/exception/UserNotFoundException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.user.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class UserNotFoundException extends CustomException { + public UserNotFoundException() { + super(ErrorCode.USER_NOT_FOUND); + } +} From 7111165f0e37923e79b0da92f31cbf7710a83269 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:08:12 +0900 Subject: [PATCH 28/64] KL-119/feat: update user info with profile image --- .../ImageUploadNotCompleteException.java | 10 +++ .../user/controller/UserController.java | 16 +++- .../user/dto/request/UserUpdateRequest.java | 14 ++++ .../klkl/domain/user/service/UserService.java | 79 +++++++++++++++---- 4 files changed, 101 insertions(+), 18 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/image/exception/ImageUploadNotCompleteException.java create mode 100644 src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java diff --git a/src/main/java/taco/klkl/domain/image/exception/ImageUploadNotCompleteException.java b/src/main/java/taco/klkl/domain/image/exception/ImageUploadNotCompleteException.java new file mode 100644 index 00000000..def934c3 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/exception/ImageUploadNotCompleteException.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.exception; + +import taco.klkl.global.error.exception.CustomException; +import taco.klkl.global.error.exception.ErrorCode; + +public class ImageUploadNotCompleteException extends CustomException { + public ImageUploadNotCompleteException() { + super(ErrorCode.IMAGE_UPLOAD_NOT_COMPLETE); + } +} diff --git a/src/main/java/taco/klkl/domain/user/controller/UserController.java b/src/main/java/taco/klkl/domain/user/controller/UserController.java index 104695fb..696131d1 100644 --- a/src/main/java/taco/klkl/domain/user/controller/UserController.java +++ b/src/main/java/taco/klkl/domain/user/controller/UserController.java @@ -1,14 +1,17 @@ package taco.klkl.domain.user.controller; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.domain.user.service.UserService; @@ -23,8 +26,13 @@ public class UserController { @Operation(summary = "내 정보 조회", description = "내 정보를 조회합니다. (테스트용)") @GetMapping("/me") - public ResponseEntity getMe() { - UserDetailResponse userDto = userService.getMyInfo(); - return ResponseEntity.ok().body(userDto); + public UserDetailResponse getMe() { + return userService.getCurrentUser(); + } + + @Operation(summary = "내 정보 수정", description = "내 정보를 수정합니다.") + @PutMapping("/me") + public UserDetailResponse updateMe(@Valid @RequestBody UserUpdateRequest request) { + return userService.updateUser(request); } } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java new file mode 100644 index 00000000..74c00d53 --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java @@ -0,0 +1,14 @@ +package taco.klkl.domain.user.dto.request; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; + +public record UserUpdateRequest( + @NotNull(message = "이름은 필수 항목입니다.") String name, + @NotNull(message = "성별은 필수 항목입니다.") String gender, + @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, + @NotBlank(message = "프로필 사진 url은 비워둘 수 없습니다.") String profileImageUrl, + String description +) { +} diff --git a/src/main/java/taco/klkl/domain/user/service/UserService.java b/src/main/java/taco/klkl/domain/user/service/UserService.java index 447da92b..1f85d4af 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserService.java +++ b/src/main/java/taco/klkl/domain/user/service/UserService.java @@ -5,44 +5,95 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.domain.UploadState; +import taco.klkl.domain.image.exception.ImageUploadNotCompleteException; import taco.klkl.domain.user.dao.UserRepository; import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; +import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; -import taco.klkl.global.common.constants.UserConstants; +import taco.klkl.global.util.ImageUtil; +import taco.klkl.global.util.UserUtil; @Slf4j @Service -@Transactional +@Transactional(readOnly = true) @RequiredArgsConstructor public class UserService { private final UserRepository userRepository; + private final UserUtil userUtil; + private final ImageUtil imageUtil; /** * 임시 나의 정보 조회 * name 속성이 "testUser"인 유저를 반환합니다. */ - public UserDetailResponse getMyInfo() { - User me = userRepository.findFirstByName(UserConstants.TEST_USER_NAME); - return UserDetailResponse.from(me); + public UserDetailResponse getCurrentUser() { + final User currentUser = userUtil.findCurrentUser(); + return UserDetailResponse.from(currentUser); } /** * - * @param userDto + * @param createRequest * @return UserDetailResponse */ - public UserDetailResponse registerUser(UserCreateRequest userDto) { - User user = User.of( - userDto.profile(), - userDto.name(), - Gender.getGenderByDescription(userDto.description()), - userDto.age(), - userDto.description() - ); + @Transactional + public UserDetailResponse createUser(final UserCreateRequest createRequest) { + validateProfileImageUrl(createRequest.profileImageUrl()); + final User user = createUserEntity(createRequest); userRepository.save(user); return UserDetailResponse.from(user); } + @Transactional + public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { + validateProfileImageUrl(updateRequest.profileImageUrl()); + User user = userUtil.findCurrentUser(); + updateUserEntity(user, updateRequest); + return UserDetailResponse.from(user); + } + + private User createUserEntity(final UserCreateRequest createRequest) { + final String profileImageUrl = createRequest.profileImageUrl(); + final String name = createRequest.name(); + final Gender gender = Gender.from(createRequest.gender()); + final Integer age = createRequest.age(); + final String description = createRequest.description(); + + return User.of( + profileImageUrl, + name, + gender, + age, + description + ); + } + + private void updateUserEntity(final User user, final UserUpdateRequest updateRequest) { + final String profileImageUrl = updateRequest.profileImageUrl(); + final String name = updateRequest.name(); + final Gender gender = Gender.from(updateRequest.gender()); + final Integer age = updateRequest.age(); + final String description = updateRequest.description(); + + user.update( + profileImageUrl, + name, + gender, + age, + description + ); + } + + private void validateProfileImageUrl(final String profileImageUrl) { + final Image image = imageUtil.findImageByImageUrl(ImageType.USER_IMAGE, profileImageUrl); + if (image.getUploadState() != UploadState.COMPLETE) { + throw new ImageUploadNotCompleteException(); + } + } + } From 4063ace23dab0083cb29928c4f28a2dcbb5ae2a8 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:08:31 +0900 Subject: [PATCH 29/64] KL-119/chore: remove unused method --- src/main/java/taco/klkl/domain/user/dao/UserRepository.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/taco/klkl/domain/user/dao/UserRepository.java b/src/main/java/taco/klkl/domain/user/dao/UserRepository.java index e3deebf4..dc9a6f24 100644 --- a/src/main/java/taco/klkl/domain/user/dao/UserRepository.java +++ b/src/main/java/taco/klkl/domain/user/dao/UserRepository.java @@ -7,5 +7,4 @@ @Repository public interface UserRepository extends JpaRepository { - User findFirstByName(String name); } From 8e5610a76d951ddc9591e87270f5a353d7c27ca9 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:10:24 +0900 Subject: [PATCH 30/64] KL-119/test: edit test by code changes --- .../controller/CommentControllerTest.java | 4 ++-- .../comment/service/CommentServiceTest.java | 4 ++-- .../service/NotificationServiceTest.java | 2 +- .../controller/ProductControllerTest.java | 8 ++++---- .../user/controller/UserControllerTest.java | 2 +- .../user/dto/request/UserRequestDtoTest.java | 6 +++--- .../dto/response/UserDetailResponseTest.java | 8 ++++---- .../dto/response/UserSimpleResponseTest.java | 8 ++++---- .../user/integration/UserIntegrationTest.java | 4 ++-- .../domain/user/service/UserServiceTest.java | 20 ++++++++++--------- 10 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java index b25d6ac8..882e4403 100644 --- a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java +++ b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java @@ -73,9 +73,9 @@ public class CommentControllerTest { ); private final User user = User.of( - requestDto.profile(), + requestDto.profileImageUrl(), requestDto.name(), - Gender.getGenderByDescription(requestDto.description()), + Gender.from(requestDto.description()), requestDto.age(), requestDto.description() ); diff --git a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java index 397af029..29cf9dd9 100644 --- a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java @@ -57,9 +57,9 @@ public class CommentServiceTest { ); private final User user = User.of( - userRequestDto.profile(), + userRequestDto.profileImageUrl(), userRequestDto.name(), - Gender.getGenderByDescription(userRequestDto.description()), + Gender.from(userRequestDto.description()), userRequestDto.age(), userRequestDto.description() ); diff --git a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java index 5c0817fd..8381fb28 100644 --- a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java +++ b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java @@ -76,7 +76,7 @@ public class NotificationServiceTest { @BeforeEach public void setUp() { - commentUser = User.of("profile", + commentUser = User.of("profileImageUrl", "윤상정", Gender.FEMALE, 26, diff --git a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java index 8b014066..4dd1389c 100644 --- a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java +++ b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java @@ -60,7 +60,7 @@ public class ProductControllerTest { void setUp() { UserDetailResponse userDetailResponse = new UserDetailResponse( 1L, - "image/profile.jpg", + "image/profileImageUrl.jpg", "userName", "userDescription", 100 @@ -268,7 +268,7 @@ void testFindProductById_ShouldReturnProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profile()))) + .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) @@ -311,7 +311,7 @@ void testCreateProduct_ShouldReturnCreatedProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profile()))) + .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) @@ -354,7 +354,7 @@ void testUpdateProduct_ShouldReturnUpdatedProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profile()))) + .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) diff --git a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java index d51393e2..37826bdf 100644 --- a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java +++ b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java @@ -40,7 +40,7 @@ public void setUp() { @DisplayName("내 정보 조회 API 테스트") public void testGetMe() throws Exception { // given - Mockito.when(userService.getMyInfo()).thenReturn(responseDto); + Mockito.when(userService.getCurrentUser()).thenReturn(responseDto); // when & then mockMvc.perform(get("/v1/users/me") diff --git a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java index 4bc801e3..43ebfe3e 100644 --- a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java @@ -24,7 +24,7 @@ public UserRequestDtoTest() { @DisplayName("유효한 UserCreateRequestDto에 대한 유효성 검사") public void testValidUserCreateRequestDto() { // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", 20, "image/profile.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest("이름", "남", 20, "image/profileImageUrl.png", "자기소개"); // when Set> violations = validator.validate(requestDto); @@ -37,7 +37,7 @@ public void testValidUserCreateRequestDto() { @DisplayName("이름이 null인 UserCreateRequest 유효성 검사") public void testInvalidUserCreateRequestDto_NameRequired() { // given - UserCreateRequest requestDto = new UserCreateRequest(null, "남", 20, "image/profile.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest(null, "남", 20, "image/profileImageUrl.png", "자기소개"); // when Set> violations = validator.validate(requestDto); @@ -50,7 +50,7 @@ public void testInvalidUserCreateRequestDto_NameRequired() { @DisplayName("나이가 음수인 UserCreateRequest 유효성 검사") public void testInvalidUserCreateRequestDto_AgeNegative() { // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", -1, "image/profile.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest("이름", "남", -1, "image/profileImageUrl.png", "자기소개"); // when Set> violations = validator.validate(requestDto); diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java index e16c78a4..ed337378 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java @@ -15,7 +15,7 @@ public class UserDetailResponseTest { public void testUserDetailResponseDto() { // given Long id = 1L; - String profile = "image/profile.png"; + String profile = "image/profileImageUrl.png"; String name = "이름"; String description = "자기소개"; int totalLikeCount = 100; @@ -25,7 +25,7 @@ public void testUserDetailResponseDto() { // then assertThat(userDetail.id()).isEqualTo(id); - assertThat(userDetail.profile()).isEqualTo(profile); + assertThat(userDetail.profileImageUrl()).isEqualTo(profile); assertThat(userDetail.name()).isEqualTo(name); assertThat(userDetail.description()).isEqualTo(description); assertThat(userDetail.totalLikeCount()).isEqualTo(totalLikeCount); @@ -35,7 +35,7 @@ public void testUserDetailResponseDto() { @DisplayName("User 객체로부터 UserDetailResponse 생성 테스트") public void testFrom() { // given - String profile = "image/profile.png"; + String profile = "image/profileImageUrl.png"; String name = "이름"; Gender gender = Gender.MALE; int age = 20; @@ -46,7 +46,7 @@ public void testFrom() { UserDetailResponse userDetail = UserDetailResponse.from(user); // then - assertThat(userDetail.profile()).isEqualTo(profile); + assertThat(userDetail.profileImageUrl()).isEqualTo(profile); assertThat(userDetail.name()).isEqualTo(name); assertThat(userDetail.description()).isEqualTo(description); assertThat(userDetail.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java index 7b006736..841c149e 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java @@ -14,7 +14,7 @@ class UserSimpleResponseTest { public void testUserSimpleResponseDto() { // given Long id = 1L; - String profile = "image/profile.png"; + String profile = "image/profileImageUrl.png"; String name = "이름"; // when @@ -22,7 +22,7 @@ public void testUserSimpleResponseDto() { // then assertThat(userSimple.id()).isEqualTo(id); - assertThat(userSimple.profile()).isEqualTo(profile); + assertThat(userSimple.profileImageUrl()).isEqualTo(profile); assertThat(userSimple.name()).isEqualTo(name); } @@ -30,7 +30,7 @@ public void testUserSimpleResponseDto() { @DisplayName("User 객체로부터 UserSimpleResponse 생성 테스트") public void testFrom() { // given - String profile = "image/profile.png"; + String profile = "image/profileImageUrl.png"; String name = "이름"; Gender gender = Gender.MALE; int age = 20; @@ -41,7 +41,7 @@ public void testFrom() { UserSimpleResponse userSimple = UserSimpleResponse.from(user); // then - assertThat(userSimple.profile()).isEqualTo(profile); + assertThat(userSimple.profileImageUrl()).isEqualTo(profile); assertThat(userSimple.name()).isEqualTo(name); } } diff --git a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java index 6266cf74..c8f3429a 100644 --- a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java @@ -31,14 +31,14 @@ public class UserIntegrationTest { @Test public void testUserMe() throws Exception { // given, when - UserDetailResponse userDto = userService.getMyInfo(); + UserDetailResponse userDto = userService.getCurrentUser(); // then mockMvc.perform(get("/v1/users/me")) .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) .andExpect(jsonPath("$.data.id", is(userDto.id().intValue()))) - .andExpect(jsonPath("$.data.profile", is(userDto.profile()))) + .andExpect(jsonPath("$.data.profile", is(userDto.profileImageUrl()))) .andExpect(jsonPath("$.data.name", is(userDto.name()))) .andExpect(jsonPath("$.data.description", is(userDto.description()))) .andExpect(jsonPath("$.timestamp", notNullValue())) diff --git a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java b/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java index 0847b7b2..fdc7cd36 100644 --- a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java +++ b/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java @@ -3,6 +3,8 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.Optional; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,16 +33,16 @@ class UserServiceTest { @Test @DisplayName("내 정보 조회 서비스 테스트") - public void testGetMyInfo() { + public void testGetCurrentUser() { // given User user = UserConstants.TEST_USER; - when(userRepository.findFirstByName(UserConstants.TEST_USER_NAME)).thenReturn(user); + when(userRepository.findById(1L)).thenReturn(Optional.of(user)); // when - UserDetailResponse userDto = userService.getMyInfo(); + UserDetailResponse userDto = userService.getCurrentUser(); // then - assertThat(userDto.profile()).isEqualTo(user.getProfile()); + assertThat(userDto.profileImageUrl()).isEqualTo(user.getProfileImageUrl()); assertThat(userDto.name()).isEqualTo(user.getName()); assertThat(userDto.description()).isEqualTo(user.getDescription()); assertThat(userDto.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); @@ -48,7 +50,7 @@ public void testGetMyInfo() { @Test @DisplayName("사용자 등록 서비스 테스트") - public void testRegisterUser() { + public void testCreateUser() { // given UserCreateRequest requestDto = new UserCreateRequest( "이상화", @@ -58,20 +60,20 @@ public void testRegisterUser() { "저는 이상화입니다." ); User user = User.of( - requestDto.profile(), + requestDto.profileImageUrl(), requestDto.name(), - Gender.getGenderByDescription(requestDto.description()), + Gender.from(requestDto.description()), requestDto.age(), requestDto.description() ); when(userRepository.save(any(User.class))).thenReturn(user); // when - UserDetailResponse responseDto = userService.registerUser(requestDto); + UserDetailResponse responseDto = userService.createUser(requestDto); // then assertThat(responseDto.name()).isEqualTo(requestDto.name()); - assertThat(responseDto.profile()).isEqualTo(requestDto.profile()); + assertThat(responseDto.profileImageUrl()).isEqualTo(requestDto.profileImageUrl()); assertThat(responseDto.description()).isEqualTo(requestDto.description()); assertThat(responseDto.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); } From 0879d18062966d9a96ee960dbf047ea46cc81a11 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:18:30 +0900 Subject: [PATCH 31/64] KL-119/chore: change s3 cloudfront domain env name --- src/main/resources/application-storage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-storage.yaml b/src/main/resources/application-storage.yaml index 2294fde8..307a1eea 100644 --- a/src/main/resources/application-storage.yaml +++ b/src/main/resources/application-storage.yaml @@ -17,4 +17,4 @@ cloud: s3: bucket: ${S3_BUCKET_NAME} cloudfront: - domain: ${CLOUDFRONT_DOMAIN} \ No newline at end of file + domain: ${S3_CLOUDFRONT_DOMAIN} \ No newline at end of file From 07c1a9aa12ad15a55fa0c1ba94ce11d458cd466c Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:27:04 +0900 Subject: [PATCH 32/64] KL-119/chore: delete test system property --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index b4a30d08..d2eb9018 100644 --- a/build.gradle +++ b/build.gradle @@ -74,5 +74,4 @@ test { useJUnitPlatform() dependsOn 'checkstyleMain' dependsOn 'checkstyleTest' - systemProperty 'spring.profiles.active', 'local,h2' } From 8bfadd1891029e43e932d82e9d6f95f6afb04917 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:27:50 +0900 Subject: [PATCH 33/64] KL-119/test: fix checkstyle errors --- src/main/java/taco/klkl/domain/image/dao/ImageRepository.java | 4 +++- src/main/java/taco/klkl/domain/user/domain/User.java | 2 +- src/main/java/taco/klkl/global/util/ImageUtil.java | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java index 9933fb07..380bd975 100644 --- a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java +++ b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java @@ -9,5 +9,7 @@ public interface ImageRepository extends JpaRepository { Optional findByImageTypeAndTargetId(final ImageType imageType, final Long targetId); - Optional findByImageTypeAndTargetIdAndImageKey(final ImageType imageType, final Long targetId, final String imageKey); + + Optional findByImageTypeAndTargetIdAndImageKey( + final ImageType imageType, final Long targetId, final String imageKey); } diff --git a/src/main/java/taco/klkl/domain/user/domain/User.java b/src/main/java/taco/klkl/domain/user/domain/User.java index f6d681ad..9826d88c 100644 --- a/src/main/java/taco/klkl/domain/user/domain/User.java +++ b/src/main/java/taco/klkl/domain/user/domain/User.java @@ -23,7 +23,7 @@ public class User { @Column( name = "profile_image_url", - length= 500, + length = 500, nullable = false ) private String profileImageUrl; diff --git a/src/main/java/taco/klkl/global/util/ImageUtil.java b/src/main/java/taco/klkl/global/util/ImageUtil.java index 0845568c..46ffd726 100644 --- a/src/main/java/taco/klkl/global/util/ImageUtil.java +++ b/src/main/java/taco/klkl/global/util/ImageUtil.java @@ -81,4 +81,4 @@ private void validateFileExtension(final FileExtension imageFileExtension, final throw new ImageUrlInvalidException(); } } -} \ No newline at end of file +} From 01b9c2201a5ee1081811ae577b3eb3dd5daf347e Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 16:46:28 +0900 Subject: [PATCH 34/64] KL-119/test: typo in comment test --- .../klkl/domain/comment/controller/CommentControllerTest.java | 3 +-- .../taco/klkl/domain/comment/service/CommentServiceTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java index 882e4403..a3fa3c25 100644 --- a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java +++ b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java @@ -71,11 +71,10 @@ public class CommentControllerTest { "image/ideal-flower.jpg", "저는 이상화입니다." ); - private final User user = User.of( requestDto.profileImageUrl(), requestDto.name(), - Gender.from(requestDto.description()), + Gender.from(requestDto.gender()), requestDto.age(), requestDto.description() ); diff --git a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java index 29cf9dd9..b1783f4a 100644 --- a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java @@ -59,7 +59,7 @@ public class CommentServiceTest { private final User user = User.of( userRequestDto.profileImageUrl(), userRequestDto.name(), - Gender.from(userRequestDto.description()), + Gender.from(userRequestDto.gender()), userRequestDto.age(), userRequestDto.description() ); From 39c5ed1eb89dd1003f6359cb868ad0bb67b228f0 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:01:21 +0900 Subject: [PATCH 35/64] KL-119/test: fix UserServiceTest error --- .../domain/user/service/UserServiceTest.java | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java b/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java index fdc7cd36..2658c88a 100644 --- a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java +++ b/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java @@ -14,12 +14,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.domain.UploadState; import taco.klkl.domain.user.dao.UserRepository; import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.global.common.constants.UserConstants; +import taco.klkl.global.util.ImageUtil; +import taco.klkl.global.util.UserUtil; @ExtendWith(MockitoExtension.class) class UserServiceTest { @@ -28,6 +33,12 @@ class UserServiceTest { @Mock UserRepository userRepository; + @Mock + UserUtil userUtil; + + @Mock + ImageUtil imageUtil; + @InjectMocks UserService userService; @@ -35,15 +46,21 @@ class UserServiceTest { @DisplayName("내 정보 조회 서비스 테스트") public void testGetCurrentUser() { // given - User user = UserConstants.TEST_USER; - when(userRepository.findById(1L)).thenReturn(Optional.of(user)); + + User user = mock(User.class); + when(userUtil.findCurrentUser()).thenReturn(user); + when(user.getId()).thenReturn(1L); + when(user.getName()).thenReturn("testUser"); + when(user.getProfileImageUrl()).thenReturn("image/test.jpg"); + when(user.getDescription()).thenReturn("테스트입니다."); // when UserDetailResponse userDto = userService.getCurrentUser(); // then - assertThat(userDto.profileImageUrl()).isEqualTo(user.getProfileImageUrl()); + assertThat(userDto.id()).isEqualTo(user.getId()); assertThat(userDto.name()).isEqualTo(user.getName()); + assertThat(userDto.profileImageUrl()).isEqualTo(user.getProfileImageUrl()); assertThat(userDto.description()).isEqualTo(user.getDescription()); assertThat(userDto.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); } @@ -62,12 +79,17 @@ public void testCreateUser() { User user = User.of( requestDto.profileImageUrl(), requestDto.name(), - Gender.from(requestDto.description()), + Gender.from(requestDto.gender()), requestDto.age(), requestDto.description() ); when(userRepository.save(any(User.class))).thenReturn(user); + // ImageUtil mock 설정 + Image mockImage = mock(Image.class); + when(mockImage.getUploadState()).thenReturn(UploadState.COMPLETE); + when(imageUtil.findImageByImageUrl(any(ImageType.class), anyString())).thenReturn(mockImage); + // when UserDetailResponse responseDto = userService.createUser(requestDto); From ceeec1803922c61f9fd06ba318d2e4fe2bafbad2 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:05:21 +0900 Subject: [PATCH 36/64] KL-119/test: fix error in user test --- .../taco/klkl/domain/user/controller/UserControllerTest.java | 2 +- .../taco/klkl/domain/user/integration/UserIntegrationTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java index 37826bdf..8e09dd39 100644 --- a/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java +++ b/src/test/java/taco/klkl/domain/user/controller/UserControllerTest.java @@ -48,7 +48,7 @@ public void testGetMe() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) .andExpect(jsonPath("$.data.id", is(nullValue()))) - .andExpect(jsonPath("$.data.profile", is(notNullValue()))) + .andExpect(jsonPath("$.data.profileImageUrl", is(notNullValue()))) .andExpect(jsonPath("$.data.name", is(responseDto.name()))) .andExpect(jsonPath("$.data.description", is(responseDto.description()))) .andExpect(jsonPath("$.data.totalLikeCount", is(UserConstants.DEFAULT_TOTAL_LIKE_COUNT))) diff --git a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java index c8f3429a..f09cb68e 100644 --- a/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java +++ b/src/test/java/taco/klkl/domain/user/integration/UserIntegrationTest.java @@ -38,7 +38,7 @@ public void testUserMe() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.isSuccess", is(true))) .andExpect(jsonPath("$.data.id", is(userDto.id().intValue()))) - .andExpect(jsonPath("$.data.profile", is(userDto.profileImageUrl()))) + .andExpect(jsonPath("$.data.profileImageUrl", is(userDto.profileImageUrl()))) .andExpect(jsonPath("$.data.name", is(userDto.name()))) .andExpect(jsonPath("$.data.description", is(userDto.description()))) .andExpect(jsonPath("$.timestamp", notNullValue())) From ef788195515c51ebf20307d82df4ddf46d4c4f8e Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:08:44 +0900 Subject: [PATCH 37/64] KL-119/test: fix error in product test --- .../domain/product/controller/ProductControllerTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java index 4dd1389c..85dfaa22 100644 --- a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java +++ b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java @@ -268,7 +268,8 @@ void testFindProductById_ShouldReturnProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) + .andExpect(jsonPath("$.data.user.profileImageUrl", + is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) @@ -311,7 +312,8 @@ void testCreateProduct_ShouldReturnCreatedProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) + .andExpect(jsonPath("$.data.user.profileImageUrl", + is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) @@ -354,7 +356,8 @@ void testUpdateProduct_ShouldReturnUpdatedProduct() throws Exception { .andExpect(jsonPath("$.data.likeCount", is(productDetailResponse.likeCount()))) .andExpect(jsonPath("$.data.rating", is(productSimpleResponse.rating()))) .andExpect(jsonPath("$.data.user.id", is(productDetailResponse.user().id().intValue()))) - .andExpect(jsonPath("$.data.user.profile", is(productDetailResponse.user().profileImageUrl()))) + .andExpect(jsonPath("$.data.user.profileImageUrl", + is(productDetailResponse.user().profileImageUrl()))) .andExpect(jsonPath("$.data.user.name", is(productDetailResponse.user().name()))) .andExpect(jsonPath("$.data.user.description", is(productDetailResponse.user().description()))) From 91d9b24ba742c48a144314b405c798efd463423e Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:33:59 +0900 Subject: [PATCH 38/64] KL-119/refactor: separate UserService --- .../klkl/domain/user/service/UserService.java | 91 +-------------- .../domain/user/service/UserServiceImpl.java | 104 ++++++++++++++++++ ...viceTest.java => UserServiceImplTest.java} | 12 +- 3 files changed, 113 insertions(+), 94 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java rename src/test/java/taco/klkl/domain/user/service/{UserServiceTest.java => UserServiceImplTest.java} (92%) diff --git a/src/main/java/taco/klkl/domain/user/service/UserService.java b/src/main/java/taco/klkl/domain/user/service/UserService.java index 1f85d4af..cbf5d993 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserService.java +++ b/src/main/java/taco/klkl/domain/user/service/UserService.java @@ -1,99 +1,16 @@ package taco.klkl.domain.user.service; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import taco.klkl.domain.image.domain.Image; -import taco.klkl.domain.image.domain.ImageType; -import taco.klkl.domain.image.domain.UploadState; -import taco.klkl.domain.image.exception.ImageUploadNotCompleteException; -import taco.klkl.domain.user.dao.UserRepository; -import taco.klkl.domain.user.domain.Gender; -import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; -import taco.klkl.global.util.ImageUtil; -import taco.klkl.global.util.UserUtil; -@Slf4j @Service -@Transactional(readOnly = true) -@RequiredArgsConstructor -public class UserService { - private final UserRepository userRepository; - private final UserUtil userUtil; - private final ImageUtil imageUtil; +public interface UserService { + UserDetailResponse getCurrentUser(); - /** - * 임시 나의 정보 조회 - * name 속성이 "testUser"인 유저를 반환합니다. - */ - public UserDetailResponse getCurrentUser() { - final User currentUser = userUtil.findCurrentUser(); - return UserDetailResponse.from(currentUser); - } - - /** - * - * @param createRequest - * @return UserDetailResponse - */ - @Transactional - public UserDetailResponse createUser(final UserCreateRequest createRequest) { - validateProfileImageUrl(createRequest.profileImageUrl()); - final User user = createUserEntity(createRequest); - userRepository.save(user); - return UserDetailResponse.from(user); - } - - @Transactional - public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { - validateProfileImageUrl(updateRequest.profileImageUrl()); - User user = userUtil.findCurrentUser(); - updateUserEntity(user, updateRequest); - return UserDetailResponse.from(user); - } - - private User createUserEntity(final UserCreateRequest createRequest) { - final String profileImageUrl = createRequest.profileImageUrl(); - final String name = createRequest.name(); - final Gender gender = Gender.from(createRequest.gender()); - final Integer age = createRequest.age(); - final String description = createRequest.description(); - - return User.of( - profileImageUrl, - name, - gender, - age, - description - ); - } - - private void updateUserEntity(final User user, final UserUpdateRequest updateRequest) { - final String profileImageUrl = updateRequest.profileImageUrl(); - final String name = updateRequest.name(); - final Gender gender = Gender.from(updateRequest.gender()); - final Integer age = updateRequest.age(); - final String description = updateRequest.description(); - - user.update( - profileImageUrl, - name, - gender, - age, - description - ); - } - - private void validateProfileImageUrl(final String profileImageUrl) { - final Image image = imageUtil.findImageByImageUrl(ImageType.USER_IMAGE, profileImageUrl); - if (image.getUploadState() != UploadState.COMPLETE) { - throw new ImageUploadNotCompleteException(); - } - } + UserDetailResponse createUser(final UserCreateRequest createRequest); + UserDetailResponse updateUser(final UserUpdateRequest updateRequest); } diff --git a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java new file mode 100644 index 00000000..b542e70c --- /dev/null +++ b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java @@ -0,0 +1,104 @@ +package taco.klkl.domain.user.service; + +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.image.domain.Image; +import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.domain.UploadState; +import taco.klkl.domain.image.exception.ImageUploadNotCompleteException; +import taco.klkl.domain.user.dao.UserRepository; +import taco.klkl.domain.user.domain.Gender; +import taco.klkl.domain.user.domain.User; +import taco.klkl.domain.user.dto.request.UserCreateRequest; +import taco.klkl.domain.user.dto.request.UserUpdateRequest; +import taco.klkl.domain.user.dto.response.UserDetailResponse; +import taco.klkl.global.util.ImageUtil; +import taco.klkl.global.util.UserUtil; + +@Slf4j +@Service +@Primary +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserUtil userUtil; + private final ImageUtil imageUtil; + + /** + * 임시 나의 정보 조회 + * name 속성이 "testUser"인 유저를 반환합니다. + */ + @Override + public UserDetailResponse getCurrentUser() { + final User currentUser = userUtil.findCurrentUser(); + return UserDetailResponse.from(currentUser); + } + + /** + * + * @param createRequest + * @return UserDetailResponse + */ + @Override + @Transactional + public UserDetailResponse createUser(final UserCreateRequest createRequest) { + validateProfileImageUrl(createRequest.profileImageUrl()); + final User user = createUserEntity(createRequest); + userRepository.save(user); + return UserDetailResponse.from(user); + } + + @Override + @Transactional + public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { + validateProfileImageUrl(updateRequest.profileImageUrl()); + User user = userUtil.findCurrentUser(); + updateUserEntity(user, updateRequest); + return UserDetailResponse.from(user); + } + + private User createUserEntity(final UserCreateRequest createRequest) { + final String profileImageUrl = createRequest.profileImageUrl(); + final String name = createRequest.name(); + final Gender gender = Gender.from(createRequest.gender()); + final Integer age = createRequest.age(); + final String description = createRequest.description(); + + return User.of( + profileImageUrl, + name, + gender, + age, + description + ); + } + + private void updateUserEntity(final User user, final UserUpdateRequest updateRequest) { + final String profileImageUrl = updateRequest.profileImageUrl(); + final String name = updateRequest.name(); + final Gender gender = Gender.from(updateRequest.gender()); + final Integer age = updateRequest.age(); + final String description = updateRequest.description(); + + user.update( + profileImageUrl, + name, + gender, + age, + description + ); + } + + private void validateProfileImageUrl(final String profileImageUrl) { + final Image image = imageUtil.findImageByImageUrl(ImageType.USER_IMAGE, profileImageUrl); + if (image.getUploadState() != UploadState.COMPLETE) { + throw new ImageUploadNotCompleteException(); + } + } + +} diff --git a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java similarity index 92% rename from src/test/java/taco/klkl/domain/user/service/UserServiceTest.java rename to src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java index 2658c88a..46ab3a08 100644 --- a/src/test/java/taco/klkl/domain/user/service/UserServiceTest.java +++ b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java @@ -3,8 +3,6 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; -import java.util.Optional; - import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,8 +25,8 @@ import taco.klkl.global.util.UserUtil; @ExtendWith(MockitoExtension.class) -class UserServiceTest { - private static final Logger log = LoggerFactory.getLogger(UserServiceTest.class); +class UserServiceImplTest { + private static final Logger log = LoggerFactory.getLogger(UserServiceImplTest.class); @Mock UserRepository userRepository; @@ -40,7 +38,7 @@ class UserServiceTest { ImageUtil imageUtil; @InjectMocks - UserService userService; + UserServiceImpl userServiceImpl; @Test @DisplayName("내 정보 조회 서비스 테스트") @@ -55,7 +53,7 @@ public void testGetCurrentUser() { when(user.getDescription()).thenReturn("테스트입니다."); // when - UserDetailResponse userDto = userService.getCurrentUser(); + UserDetailResponse userDto = userServiceImpl.getCurrentUser(); // then assertThat(userDto.id()).isEqualTo(user.getId()); @@ -91,7 +89,7 @@ public void testCreateUser() { when(imageUtil.findImageByImageUrl(any(ImageType.class), anyString())).thenReturn(mockImage); // when - UserDetailResponse responseDto = userService.createUser(requestDto); + UserDetailResponse responseDto = userServiceImpl.createUser(requestDto); // then assertThat(responseDto.name()).isEqualTo(requestDto.name()); From 5a9237ceb78ebad1308044f32a8b8a96718a25ae Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:53:20 +0900 Subject: [PATCH 39/64] KL-119/feat: create product image upload presigned url --- .../image/controller/ImageController.java | 16 +++++++++- .../request/ProductImageUploadRequest.java | 10 ++++++ .../domain/image/service/ImageService.java | 4 +++ .../image/service/ImageServiceImpl.java | 31 +++++++++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index ec3c64e4..ad97fd53 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -1,5 +1,6 @@ package taco.klkl.domain.image.controller; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -9,6 +10,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @@ -28,7 +30,7 @@ public class ImageController { ) @PostMapping("/v1/users/me/upload-url") public PresignedUrlResponse createUserImageUploadUrl( - @Valid @RequestBody UserImageUploadRequest request + @Valid @RequestBody final UserImageUploadRequest request ) { return imageService.createUserImageUploadUrl(request); } @@ -41,4 +43,16 @@ public PresignedUrlResponse createUserImageUploadUrl( public ImageUrlResponse uploadCompleteUserImage() { return imageService.uploadCompleteUserImage(); } + + @Operation( + summary = "상품 이미지 업로드 Presigned URL 생성", + description = "상품 이미지 업로드를 위한 Presigned URL를 생성합니다." + ) + @PostMapping("/v1/products/{productId}/upload-url") + public PresignedUrlResponse createProductImageUploadUrl( + @PathVariable final Long productId, + @Valid @RequestBody final ProductImageUploadRequest request + ) { + return imageService.createProductImageUploadUrl(productId, request); + } } diff --git a/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java new file mode 100644 index 00000000..d737d927 --- /dev/null +++ b/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java @@ -0,0 +1,10 @@ +package taco.klkl.domain.image.dto.request; + +import jakarta.validation.constraints.NotBlank; +import taco.klkl.global.common.constants.ImageValidationMessages; + +public record ProductImageUploadRequest( + @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) + String fileExtension +) { +} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index 6ce81d5f..88ee6712 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -2,6 +2,7 @@ import org.springframework.stereotype.Service; +import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @@ -12,4 +13,7 @@ public interface ImageService { PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest createRequest); ImageUrlResponse uploadCompleteUserImage(); + + PresignedUrlResponse createProductImageUploadUrl(final Long productId, final ProductImageUploadRequest uploadRequest); + } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index b91b4d43..ee18815a 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -19,6 +19,7 @@ import taco.klkl.domain.image.domain.FileExtension; import taco.klkl.domain.image.domain.Image; import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @@ -91,6 +92,36 @@ public ImageUrlResponse uploadCompleteUserImage() { return ImageUrlResponse.from(imageUrl); } + @Override + @Transactional + public PresignedUrlResponse createProductImageUploadUrl( + final Long productId, + final ProductImageUploadRequest uploadRequest + ) { + final ImageType imageType = ImageType.PRODUCT_IMAGE; + final String imageKey = ImageKeyGenerator.generate(); + final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); + + final Image image = createImageEntity( + imageType, + productId, + imageKey, + fileExtension + ); + imageRepository.save(image); + + final PutObjectRequest putObjectRequest = createPutObjectRequest( + image.createFileName(), + image.getFileExtension() + ); + final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); + + final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); + final String presignedUrl = presignedRequest.url().toString(); + + return PresignedUrlResponse.from(presignedUrl); + } + private PutObjectRequest createPutObjectRequest( final String fileName, final FileExtension fileExtension From b3d1e601f39c5753e5da0dc134b0f027ef6b878a Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 17:56:12 +0900 Subject: [PATCH 40/64] KL-119/feat: upload complete product image --- .../domain/image/controller/ImageController.java | 11 +++++++++++ .../klkl/domain/image/service/ImageService.java | 7 ++++++- .../klkl/domain/image/service/ImageServiceImpl.java | 13 +++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index ad97fd53..f96650f6 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -55,4 +55,15 @@ public PresignedUrlResponse createProductImageUploadUrl( ) { return imageService.createProductImageUploadUrl(productId, request); } + + @Operation( + summary = "상품 이미지 업로드 완료 처리", + description = "상품 이미지 업로드를 완료 처리합니다." + ) + @PostMapping("/v1/products/{productId}/upload-complete") + public ImageUrlResponse uploadCompleteProductImage( + @PathVariable final Long productId + ) { + return imageService.uploadCompleteProductImage(productId); + } } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index 88ee6712..8bc6a499 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -14,6 +14,11 @@ public interface ImageService { ImageUrlResponse uploadCompleteUserImage(); - PresignedUrlResponse createProductImageUploadUrl(final Long productId, final ProductImageUploadRequest uploadRequest); + PresignedUrlResponse createProductImageUploadUrl( + final Long productId, + final ProductImageUploadRequest uploadRequest + ); + + ImageUrlResponse uploadCompleteProductImage(final Long productId); } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index ee18815a..1fa78f6a 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -122,6 +122,19 @@ public PresignedUrlResponse createProductImageUploadUrl( return PresignedUrlResponse.from(presignedUrl); } + @Override + @Transactional + public ImageUrlResponse uploadCompleteProductImage(final Long productId) { + final ImageType imageType = ImageType.PRODUCT_IMAGE; + + final Image image = imageRepository.findByImageTypeAndTargetId(imageType, productId) + .orElseThrow(ImageNotFoundException::new); + + image.uploadComplete(); + final String imageUrl = createImageUrl(image); + return ImageUrlResponse.from(imageUrl); + } + private PutObjectRequest createPutObjectRequest( final String fileName, final FileExtension fileExtension From b40e042269949cb4818c16694810c7f2829186ef Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:11:01 +0900 Subject: [PATCH 41/64] KL-119/feat: update user image when upload complete --- .../domain/image/controller/ImageController.java | 6 ++++-- .../klkl/domain/image/service/ImageService.java | 2 +- .../klkl/domain/image/service/ImageServiceImpl.java | 4 ++-- .../java/taco/klkl/domain/user/domain/User.java | 13 +++++++------ .../domain/user/dto/request/UserCreateRequest.java | 2 -- .../domain/user/dto/request/UserUpdateRequest.java | 2 -- .../klkl/domain/user/service/UserServiceImpl.java | 6 ------ .../klkl/global/common/constants/UserConstants.java | 1 - 8 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index f96650f6..a3a84c2e 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -1,5 +1,6 @@ package taco.klkl.domain.image.controller; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -40,8 +41,9 @@ public PresignedUrlResponse createUserImageUploadUrl( description = "유저 이미지 업로드를 완료 처리합니다." ) @PostMapping("/v1/users/me/upload-complete") - public ImageUrlResponse uploadCompleteUserImage() { - return imageService.uploadCompleteUserImage(); + public ResponseEntity uploadCompleteUserImage() { + imageService.uploadCompleteUserImage(); + return ResponseEntity.ok().build(); } @Operation( diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index 8bc6a499..9d64a874 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -12,7 +12,7 @@ public interface ImageService { PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest createRequest); - ImageUrlResponse uploadCompleteUserImage(); + void uploadCompleteUserImage(); PresignedUrlResponse createProductImageUploadUrl( final Long productId, diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 1fa78f6a..7546dcf8 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -80,7 +80,7 @@ public PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadReques @Override @Transactional - public ImageUrlResponse uploadCompleteUserImage() { + public void uploadCompleteUserImage() { final ImageType imageType = ImageType.USER_IMAGE; final User currentUser = userUtil.findCurrentUser(); @@ -89,7 +89,7 @@ public ImageUrlResponse uploadCompleteUserImage() { image.uploadComplete(); final String imageUrl = createImageUrl(image); - return ImageUrlResponse.from(imageUrl); + currentUser.updateProfileImageUrl(imageUrl); } @Override diff --git a/src/main/java/taco/klkl/domain/user/domain/User.java b/src/main/java/taco/klkl/domain/user/domain/User.java index 9826d88c..62b0abb9 100644 --- a/src/main/java/taco/klkl/domain/user/domain/User.java +++ b/src/main/java/taco/klkl/domain/user/domain/User.java @@ -10,6 +10,7 @@ import jakarta.persistence.PrePersist; import lombok.Getter; import lombok.NoArgsConstructor; +import taco.klkl.global.common.constants.UserConstants; @Getter @NoArgsConstructor @@ -69,13 +70,12 @@ protected void prePersist() { } private User( - final String profileImageUrl, final String name, final Gender gender, final Integer age, final String description ) { - this.profileImageUrl = profileImageUrl; + this.profileImageUrl = UserConstants.DEFAULT_PROFILE; this.name = name; this.gender = gender; this.age = age; @@ -83,26 +83,27 @@ private User( } public static User of( - final String profileImageUrl, final String name, final Gender gender, final Integer age, final String description ) { - return new User(profileImageUrl, name, gender, age, description); + return new User(name, gender, age, description); } public void update( - final String profileImageUrl, final String name, final Gender gender, final Integer age, final String description ) { - this.profileImageUrl = profileImageUrl; this.name = name; this.gender = gender; this.age = age; this.description = description; } + + public void updateProfileImageUrl(final String profileImageUrl) { + this.profileImageUrl = profileImageUrl; + } } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java index 4ec83b9e..f3bd1815 100644 --- a/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserCreateRequest.java @@ -1,6 +1,5 @@ package taco.klkl.domain.user.dto.request; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @@ -8,7 +7,6 @@ public record UserCreateRequest( @NotNull(message = "이름은 필수 항목입니다.") String name, @NotNull(message = "성별은 필수 항목입니다.") String gender, @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, - @NotBlank(message = "프로필 사진 url은 비워둘 수 없습니다.") String profileImageUrl, String description ) { } diff --git a/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java index 74c00d53..42bede10 100644 --- a/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java +++ b/src/main/java/taco/klkl/domain/user/dto/request/UserUpdateRequest.java @@ -1,6 +1,5 @@ package taco.klkl.domain.user.dto.request; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @@ -8,7 +7,6 @@ public record UserUpdateRequest( @NotNull(message = "이름은 필수 항목입니다.") String name, @NotNull(message = "성별은 필수 항목입니다.") String gender, @PositiveOrZero(message = "나이는 0 이상이어야 합니다.") Integer age, - @NotBlank(message = "프로필 사진 url은 비워둘 수 없습니다.") String profileImageUrl, String description ) { } diff --git a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java index b542e70c..7b59f4a3 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java +++ b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java @@ -47,7 +47,6 @@ public UserDetailResponse getCurrentUser() { @Override @Transactional public UserDetailResponse createUser(final UserCreateRequest createRequest) { - validateProfileImageUrl(createRequest.profileImageUrl()); final User user = createUserEntity(createRequest); userRepository.save(user); return UserDetailResponse.from(user); @@ -56,21 +55,18 @@ public UserDetailResponse createUser(final UserCreateRequest createRequest) { @Override @Transactional public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { - validateProfileImageUrl(updateRequest.profileImageUrl()); User user = userUtil.findCurrentUser(); updateUserEntity(user, updateRequest); return UserDetailResponse.from(user); } private User createUserEntity(final UserCreateRequest createRequest) { - final String profileImageUrl = createRequest.profileImageUrl(); final String name = createRequest.name(); final Gender gender = Gender.from(createRequest.gender()); final Integer age = createRequest.age(); final String description = createRequest.description(); return User.of( - profileImageUrl, name, gender, age, @@ -79,14 +75,12 @@ private User createUserEntity(final UserCreateRequest createRequest) { } private void updateUserEntity(final User user, final UserUpdateRequest updateRequest) { - final String profileImageUrl = updateRequest.profileImageUrl(); final String name = updateRequest.name(); final Gender gender = Gender.from(updateRequest.gender()); final Integer age = updateRequest.age(); final String description = updateRequest.description(); user.update( - profileImageUrl, name, gender, age, diff --git a/src/main/java/taco/klkl/global/common/constants/UserConstants.java b/src/main/java/taco/klkl/global/common/constants/UserConstants.java index e9711c38..563b2cdc 100644 --- a/src/main/java/taco/klkl/global/common/constants/UserConstants.java +++ b/src/main/java/taco/klkl/global/common/constants/UserConstants.java @@ -7,7 +7,6 @@ public final class UserConstants { public static final String TEST_USER_NAME = "testUser"; public static final User TEST_USER = User.of( - "image/test.jpg", TEST_USER_NAME, Gender.MALE, 20, From 8815214e7b8f0cf691c455489b2f4f834da892d3 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:27:47 +0900 Subject: [PATCH 42/64] KL-119/refactor: add final keyword and private constructor --- .../klkl/global/common/constants/NotificationConstants.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/taco/klkl/global/common/constants/NotificationConstants.java b/src/main/java/taco/klkl/global/common/constants/NotificationConstants.java index 402d8e92..6efd285f 100644 --- a/src/main/java/taco/klkl/global/common/constants/NotificationConstants.java +++ b/src/main/java/taco/klkl/global/common/constants/NotificationConstants.java @@ -1,9 +1,12 @@ package taco.klkl.global.common.constants; -public class NotificationConstants { +public final class NotificationConstants { public static final String DEFAULT_IS_READ_STRING = "false"; public static final boolean DEFAULT_IS_READ_VALUE = false; public static final int DEFAULT_PAGE_SIZE = 5; public static final boolean UPDATED_IS_READ_VALUE = true; + + private NotificationConstants() { + } } From b1a807a7b98380921198a4ee5365b4bbe79a5fb2 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:42:04 +0900 Subject: [PATCH 43/64] KL-119/refactor: separate update product tags --- .../domain/product/service/ProductServiceImpl.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java b/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java index 246b22fe..a3a11ae8 100644 --- a/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java +++ b/src/main/java/taco/klkl/domain/product/service/ProductServiceImpl.java @@ -127,10 +127,7 @@ public ProductDetailResponse updateProduct(final Long id, final ProductCreateUpd final Product product = productRepository.findById(id) .orElseThrow(ProductNotFoundException::new); updateProductEntity(product, updateRequest); - if (updateRequest.tagIds() != null) { - Set updatedTags = createTagsByTagIds(updateRequest.tagIds()); - product.updateTags(updatedTags); - } + updateProductEntityTags(product, updateRequest.tagIds()); return ProductDetailResponse.from(product); } @@ -287,6 +284,13 @@ private void updateProductEntity(final Product product, final ProductCreateUpdat ); } + private void updateProductEntityTags(final Product product, final Set tagIds) { + if (tagIds != null) { + final Set updatedTags = createTagsByTagIds(tagIds); + product.updateTags(updatedTags); + } + } + private Sort.Direction createSortDirectionByQuery(final String query) throws SortDirectionNotFoundException { try { return Sort.Direction.fromString(query); From c15584853cd80dbd24fce089c8bddd11688b7a79 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:43:47 +0900 Subject: [PATCH 44/64] KL-119/remove: remove image util --- .../java/taco/klkl/global/util/ImageUtil.java | 84 ------------------- 1 file changed, 84 deletions(-) delete mode 100644 src/main/java/taco/klkl/global/util/ImageUtil.java diff --git a/src/main/java/taco/klkl/global/util/ImageUtil.java b/src/main/java/taco/klkl/global/util/ImageUtil.java deleted file mode 100644 index 46ffd726..00000000 --- a/src/main/java/taco/klkl/global/util/ImageUtil.java +++ /dev/null @@ -1,84 +0,0 @@ -package taco.klkl.global.util; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import lombok.RequiredArgsConstructor; -import taco.klkl.domain.image.dao.ImageRepository; -import taco.klkl.domain.image.domain.FileExtension; -import taco.klkl.domain.image.domain.Image; -import taco.klkl.domain.image.domain.ImageType; -import taco.klkl.domain.image.exception.ImageNotFoundException; -import taco.klkl.domain.image.exception.ImageUrlInvalidException; - -@Component -@RequiredArgsConstructor -public class ImageUtil { - - private static final Pattern URL_PATTERN = Pattern.compile("https://([^/]+)/([^/]+)/(\\d+)/([^/]+)\\.(\\w+)"); - - private final ImageRepository imageRepository; - - @Value("${cloud.aws.cloudfront.domain}") - private String cloudFrontDomain; - - public Image findImageByImageUrl(final ImageType imageType, final String imageUrl) { - final Matcher matcher = parseUrl(imageUrl); - validateUrlFormat(matcher); - validateUrlComponents(matcher, imageType); - - final String userId = matcher.group(3); - final String imageKey = matcher.group(4); - final String fileExtension = matcher.group(5); - - final Long parsedUserId = parseUserId(userId); - final Image image = findImageByUrlComponents(imageType, parsedUserId, imageKey); - validateFileExtension(image.getFileExtension(), fileExtension); - - return image; - } - - private Image findImageByUrlComponents(final ImageType imageType, final Long userId, final String imageKey) { - return imageRepository.findByImageTypeAndTargetIdAndImageKey(imageType, userId, imageKey) - .orElseThrow(ImageNotFoundException::new); - } - - private Matcher parseUrl(final String imageUrl) { - return URL_PATTERN.matcher(imageUrl); - } - - private Long parseUserId(final String userId) { - try { - return Long.parseLong(userId); - } catch (NumberFormatException e) { - throw new ImageUrlInvalidException(); - } - } - - private void validateUrlFormat(final Matcher matcher) { - if (!matcher.matches()) { - throw new ImageUrlInvalidException(); - } - } - - private void validateUrlComponents(final Matcher matcher, final ImageType expectedImageType) { - final String domain = matcher.group(1); - final String imageType = matcher.group(2); - - if (!domain.equals(cloudFrontDomain)) { - throw new ImageUrlInvalidException(); - } - if (!expectedImageType.getValue().equals(imageType)) { - throw new ImageUrlInvalidException(); - } - } - - private void validateFileExtension(final FileExtension imageFileExtension, final String urlFileExtension) { - if (!imageFileExtension.getValue().equals(urlFileExtension)) { - throw new ImageUrlInvalidException(); - } - } -} From db21cfe20a40940df816af9f95046482044391e9 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:44:09 +0900 Subject: [PATCH 45/64] KL-119/chore: remove validateProfileImageUrl --- .../domain/user/service/UserServiceImpl.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java index 7b59f4a3..3293f0fd 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java +++ b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java @@ -6,17 +6,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import taco.klkl.domain.image.domain.Image; -import taco.klkl.domain.image.domain.ImageType; -import taco.klkl.domain.image.domain.UploadState; -import taco.klkl.domain.image.exception.ImageUploadNotCompleteException; import taco.klkl.domain.user.dao.UserRepository; import taco.klkl.domain.user.domain.Gender; import taco.klkl.domain.user.domain.User; import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.request.UserUpdateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; -import taco.klkl.global.util.ImageUtil; import taco.klkl.global.util.UserUtil; @Slf4j @@ -25,9 +20,10 @@ @Transactional(readOnly = true) @RequiredArgsConstructor public class UserServiceImpl implements UserService { + private final UserRepository userRepository; + private final UserUtil userUtil; - private final ImageUtil imageUtil; /** * 임시 나의 정보 조회 @@ -87,12 +83,4 @@ private void updateUserEntity(final User user, final UserUpdateRequest updateReq description ); } - - private void validateProfileImageUrl(final String profileImageUrl) { - final Image image = imageUtil.findImageByImageUrl(ImageType.USER_IMAGE, profileImageUrl); - if (image.getUploadState() != UploadState.COMPLETE) { - throw new ImageUploadNotCompleteException(); - } - } - } From 3d0888b9c54b7c568ff1daa78b909141b8fc24b9 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 18:45:04 +0900 Subject: [PATCH 46/64] KL-119/feat: add ProductImage --- .../klkl/domain/product/domain/Product.java | 67 ++++++++++++------- .../domain/product/domain/ProductImage.java | 63 +++++++++++++++++ 2 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/product/domain/ProductImage.java diff --git a/src/main/java/taco/klkl/domain/product/domain/Product.java b/src/main/java/taco/klkl/domain/product/domain/Product.java index 0d504bff..85581389 100644 --- a/src/main/java/taco/klkl/domain/product/domain/Product.java +++ b/src/main/java/taco/klkl/domain/product/domain/Product.java @@ -1,8 +1,11 @@ package taco.klkl.domain.product.domain; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.stream.IntStream; import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.DynamicInsert; @@ -18,6 +21,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; import jakarta.persistence.PrePersist; import lombok.AccessLevel; import lombok.Getter; @@ -44,6 +48,14 @@ public class Product { @Column(name = "product_id") private Long id; + @OneToMany( + mappedBy = "product", + cascade = CascadeType.ALL, + orphanRemoval = true + ) + @OrderBy("orderIndex ASC") + private List images = new ArrayList<>(); + @Column( name = "name", length = ProductConstants.NAME_MAX_LENGTH, @@ -153,30 +165,6 @@ protected void prePersist() { } } - private Product( - final String name, - final String description, - final String address, - final Integer price, - final Rating rating, - final User user, - final City city, - final Subcategory subcategory, - final Currency currency - ) { - this.name = name; - this.description = description; - this.address = address; - this.price = price; - this.rating = rating; - this.user = user; - this.city = city; - this.subcategory = subcategory; - this.currency = currency; - this.likeCount = DefaultConstants.DEFAULT_INT_VALUE; - this.createdAt = LocalDateTime.now(); - } - public static Product of( final String name, final String description, @@ -242,4 +230,35 @@ public int decreaseLikeCount() throws LikeCountBelowMinimumException { return this.likeCount; } + + public void updateImages(final List imageUrls) { + this.images.clear(); + this.images = IntStream.range(0, imageUrls.size()) + .mapToObj(i -> ProductImage.of(this, imageUrls.get(i), i)) + .toList(); + } + + private Product( + final String name, + final String description, + final String address, + final Integer price, + final Rating rating, + final User user, + final City city, + final Subcategory subcategory, + final Currency currency + ) { + this.name = name; + this.description = description; + this.address = address; + this.price = price; + this.rating = rating; + this.user = user; + this.city = city; + this.subcategory = subcategory; + this.currency = currency; + this.likeCount = DefaultConstants.DEFAULT_INT_VALUE; + this.createdAt = LocalDateTime.now(); + } } diff --git a/src/main/java/taco/klkl/domain/product/domain/ProductImage.java b/src/main/java/taco/klkl/domain/product/domain/ProductImage.java new file mode 100644 index 00000000..f5077d1b --- /dev/null +++ b/src/main/java/taco/klkl/domain/product/domain/ProductImage.java @@ -0,0 +1,63 @@ +package taco.klkl.domain.product.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity(name = "product_image") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProductImage { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne( + fetch = FetchType.LAZY, + optional = false + ) + @JoinColumn( + name = "product_id", + nullable = false + ) + private Product product; + + @Column( + name = "image_url", + nullable = false + ) + private String imageUrl; + + @Column( + name = "order_index", + nullable= false + ) + private Integer orderIndex; + + public static ProductImage of( + final Product product, + final String imageUrl, + final Integer orderIndex + ) { + return new ProductImage(product, imageUrl, orderIndex); + } + + private ProductImage( + final Product product, + final String imageUrl, + final Integer orderIndex + ) { + this.product = product; + this.imageUrl = imageUrl; + this.orderIndex = orderIndex; + } +} \ No newline at end of file From c809faf5f99bb3334f67fc4e9af9ab22ec0a4238 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Tue, 27 Aug 2024 19:02:48 +0900 Subject: [PATCH 47/64] KL-119/feat: update product images when upload complete --- .../image/controller/ImageController.java | 6 ++-- .../domain/image/dao/ImageRepository.java | 4 +-- .../image/dto/response/ImageUrlResponse.java | 9 ----- .../domain/image/service/ImageService.java | 3 +- .../image/service/ImageServiceImpl.java | 33 ++++++++++++++----- 5 files changed, 31 insertions(+), 24 deletions(-) delete mode 100644 src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index a3a84c2e..675f3433 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -13,7 +13,6 @@ import lombok.extern.slf4j.Slf4j; import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; -import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.service.ImageService; @@ -63,9 +62,10 @@ public PresignedUrlResponse createProductImageUploadUrl( description = "상품 이미지 업로드를 완료 처리합니다." ) @PostMapping("/v1/products/{productId}/upload-complete") - public ImageUrlResponse uploadCompleteProductImage( + public ResponseEntity uploadCompleteProductImage( @PathVariable final Long productId ) { - return imageService.uploadCompleteProductImage(productId); + imageService.uploadCompleteProductImage(productId); + return ResponseEntity.ok().build(); } } diff --git a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java index 380bd975..22481656 100644 --- a/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java +++ b/src/main/java/taco/klkl/domain/image/dao/ImageRepository.java @@ -1,5 +1,6 @@ package taco.klkl.domain.image.dao; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,6 +11,5 @@ public interface ImageRepository extends JpaRepository { Optional findByImageTypeAndTargetId(final ImageType imageType, final Long targetId); - Optional findByImageTypeAndTargetIdAndImageKey( - final ImageType imageType, final Long targetId, final String imageKey); + List findAllByImageTypeAndTargetId(final ImageType imageType, final Long targetId); } diff --git a/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java b/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java deleted file mode 100644 index 8aec439e..00000000 --- a/src/main/java/taco/klkl/domain/image/dto/response/ImageUrlResponse.java +++ /dev/null @@ -1,9 +0,0 @@ -package taco.klkl.domain.image.dto.response; - -public record ImageUrlResponse( - String imageUrl -) { - public static ImageUrlResponse from(final String imageUrl) { - return new ImageUrlResponse(imageUrl); - } -} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index 9d64a874..bffdbad2 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -4,7 +4,6 @@ import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; -import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @Service @@ -19,6 +18,6 @@ PresignedUrlResponse createProductImageUploadUrl( final ProductImageUploadRequest uploadRequest ); - ImageUrlResponse uploadCompleteProductImage(final Long productId); + void uploadCompleteProductImage(final Long productId); } diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 7546dcf8..898e317c 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -1,6 +1,7 @@ package taco.klkl.domain.image.service; import java.time.Duration; +import java.util.List; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Primary; @@ -19,12 +20,14 @@ import taco.klkl.domain.image.domain.FileExtension; import taco.klkl.domain.image.domain.Image; import taco.klkl.domain.image.domain.ImageType; +import taco.klkl.domain.image.domain.UploadState; import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; import taco.klkl.domain.image.dto.request.UserImageUploadRequest; -import taco.klkl.domain.image.dto.response.ImageUrlResponse; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.exception.ImageNotFoundException; +import taco.klkl.domain.product.domain.Product; import taco.klkl.domain.user.domain.User; +import taco.klkl.global.util.ProductUtil; import taco.klkl.global.util.UserUtil; @Slf4j @@ -43,6 +46,7 @@ public class ImageServiceImpl implements ImageService { private final ImageRepository imageRepository; private final UserUtil userUtil; + private final ProductUtil productUtil; @Value("${cloud.aws.s3.bucket}") private String bucketName; @@ -124,15 +128,24 @@ public PresignedUrlResponse createProductImageUploadUrl( @Override @Transactional - public ImageUrlResponse uploadCompleteProductImage(final Long productId) { + public void uploadCompleteProductImage(final Long productId) { final ImageType imageType = ImageType.PRODUCT_IMAGE; - final Image image = imageRepository.findByImageTypeAndTargetId(imageType, productId) - .orElseThrow(ImageNotFoundException::new); - - image.uploadComplete(); - final String imageUrl = createImageUrl(image); - return ImageUrlResponse.from(imageUrl); + final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, productId); + + images.stream() + .filter(image -> image.getUploadState() == UploadState.COMPLETE) + .forEach(this::deleteImageEntity); + + final List newImages = images.stream() + .filter(image -> image.getUploadState() == UploadState.PENDING) + .toList(); + newImages.forEach(Image::uploadComplete); + final List imageUrls = newImages.stream() + .map(this::createImageUrl) + .toList(); + Product product = productUtil.findProductEntityById(productId); + product.updateImages(imageUrls); } private PutObjectRequest createPutObjectRequest( @@ -165,6 +178,10 @@ private Image createImageEntity( return Image.of(imageType, targetId, imageKey, fileExtension); } + private void deleteImageEntity(final Image image) { + imageRepository.delete(image); + } + private String createImageUrl(final Image image) { return "https://" + cloudFrontDomain + "/" + image.createFileName(); } From 75d038a9c344f642c5d7c5c86a89fe16a135465c Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 14:41:23 +0900 Subject: [PATCH 48/64] feat: delete previously upload complete user images --- .../domain/image/service/ImageServiceImpl.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 898e317c..43fe1939 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -88,11 +88,18 @@ public void uploadCompleteUserImage() { final ImageType imageType = ImageType.USER_IMAGE; final User currentUser = userUtil.findCurrentUser(); - final Image image = imageRepository.findByImageTypeAndTargetId(imageType, currentUser.getId()) - .orElseThrow(ImageNotFoundException::new); + final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, currentUser.getId()); - image.uploadComplete(); - final String imageUrl = createImageUrl(image); + images.stream() + .filter(image -> image.getUploadState() == UploadState.COMPLETE) + .forEach(this::deleteImageEntity); + + final Image newImage = images.stream() + .filter(image -> image.getUploadState() == UploadState.PENDING) + .findFirst() + .orElseThrow(ImageNotFoundException::new); + newImage.uploadComplete(); + final String imageUrl = createImageUrl(newImage); currentUser.updateProfileImageUrl(imageUrl); } From b79c4b43033534a07cdcd102aa925f14b78633da Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 16:05:57 +0900 Subject: [PATCH 49/64] KL-119/chore: change aws sdk release version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d2eb9018..acc3f369 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' // S3 - implementation platform('software.amazon.awssdk:bom:2.20.56') + implementation platform('software.amazon.awssdk:bom:2.21.1') implementation 'software.amazon.awssdk:s3' } From 801a1b96bd8b09156241cac85c09afb80bced091 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 16:23:36 +0900 Subject: [PATCH 50/64] KL-119/refactor: rename enum and reduce duplicate codes --- .../taco/klkl/domain/image/domain/Image.java | 4 + .../klkl/domain/image/domain/UploadState.java | 1 + .../image/service/ImageServiceImpl.java | 124 ++++++------------ 3 files changed, 46 insertions(+), 83 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java index 6cc0b663..3d2c04d5 100644 --- a/src/main/java/taco/klkl/domain/image/domain/Image.java +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -92,6 +92,10 @@ public void uploadComplete() { this.uploadState = UploadState.COMPLETE; } + public void markAsDeprecated() { + this.uploadState = UploadState.DEPRECATED; + } + public String createFileName() { return imageType.getValue() + "/" + targetId + "/" diff --git a/src/main/java/taco/klkl/domain/image/domain/UploadState.java b/src/main/java/taco/klkl/domain/image/domain/UploadState.java index 9763b8c2..06aaba1e 100644 --- a/src/main/java/taco/klkl/domain/image/domain/UploadState.java +++ b/src/main/java/taco/klkl/domain/image/domain/UploadState.java @@ -11,6 +11,7 @@ public enum UploadState { PENDING("대기중"), COMPLETE("완료"), + DEPRECATED("폐기예정"), ; private final String value; diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 43fe1939..6a935f8b 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -42,9 +42,7 @@ public class ImageServiceImpl implements ImageService { private final S3Client s3Client; private final S3Presigner s3Presigner; - private final ImageRepository imageRepository; - private final UserUtil userUtil; private final ProductUtil productUtil; @@ -57,74 +55,54 @@ public class ImageServiceImpl implements ImageService { @Override @Transactional public PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest uploadRequest) { - final ImageType imageType = ImageType.USER_IMAGE; final User currentUser = userUtil.findCurrentUser(); - final String imageKey = ImageKeyGenerator.generate(); - final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); - - final Image image = createImageEntity( - imageType, - currentUser.getId(), - imageKey, - fileExtension - ); - imageRepository.save(image); - - final PutObjectRequest putObjectRequest = createPutObjectRequest( - image.createFileName(), - image.getFileExtension() - ); - final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); - - final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); - final String presignedUrl = presignedRequest.url().toString(); + return createImageUploadUrl(ImageType.USER_IMAGE, currentUser.getId(), uploadRequest.fileExtension()); + } - return PresignedUrlResponse.from(presignedUrl); + @Override + @Transactional + public PresignedUrlResponse createProductImageUploadUrl( + final Long productId, + final ProductImageUploadRequest uploadRequest + ) { + return createImageUploadUrl(ImageType.PRODUCT_IMAGE, productId, uploadRequest.fileExtension()); } @Override @Transactional public void uploadCompleteUserImage() { - final ImageType imageType = ImageType.USER_IMAGE; final User currentUser = userUtil.findCurrentUser(); - - final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, currentUser.getId()); - - images.stream() - .filter(image -> image.getUploadState() == UploadState.COMPLETE) - .forEach(this::deleteImageEntity); + final List images = uploadCompleteImage(ImageType.USER_IMAGE, currentUser.getId()); final Image newImage = images.stream() - .filter(image -> image.getUploadState() == UploadState.PENDING) + .filter(image -> image.getUploadState() == UploadState.COMPLETE) .findFirst() .orElseThrow(ImageNotFoundException::new); - newImage.uploadComplete(); + final String imageUrl = createImageUrl(newImage); currentUser.updateProfileImageUrl(imageUrl); } @Override @Transactional - public PresignedUrlResponse createProductImageUploadUrl( - final Long productId, - final ProductImageUploadRequest uploadRequest - ) { - final ImageType imageType = ImageType.PRODUCT_IMAGE; + public void uploadCompleteProductImage(final Long productId) { + final List newImages = uploadCompleteImage(ImageType.PRODUCT_IMAGE, productId); + + final List imageUrls = newImages.stream() + .map(this::createImageUrl) + .toList(); + + Product product = productUtil.findProductEntityById(productId); + product.updateImages(imageUrls); + } + + private PresignedUrlResponse createImageUploadUrl(ImageType imageType, Long targetId, String fileExtensionStr) { final String imageKey = ImageKeyGenerator.generate(); - final FileExtension fileExtension = FileExtension.from(uploadRequest.fileExtension()); - - final Image image = createImageEntity( - imageType, - productId, - imageKey, - fileExtension - ); - imageRepository.save(image); - - final PutObjectRequest putObjectRequest = createPutObjectRequest( - image.createFileName(), - image.getFileExtension() - ); + final FileExtension fileExtension = FileExtension.from(fileExtensionStr); + + final Image image = createAndSaveImageEntity(imageType, targetId, imageKey, fileExtension); + + final PutObjectRequest putObjectRequest = createPutObjectRequest(image.createFileName(), image.getFileExtension()); final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); @@ -133,32 +111,27 @@ public PresignedUrlResponse createProductImageUploadUrl( return PresignedUrlResponse.from(presignedUrl); } - @Override - @Transactional - public void uploadCompleteProductImage(final Long productId) { - final ImageType imageType = ImageType.PRODUCT_IMAGE; + private Image createAndSaveImageEntity(ImageType imageType, Long targetId, String imageKey, FileExtension fileExtension) { + final Image image = Image.of(imageType, targetId, imageKey, fileExtension); + return imageRepository.save(image); + } - final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, productId); + private List uploadCompleteImage(ImageType imageType, Long targetId) { + final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, targetId); images.stream() .filter(image -> image.getUploadState() == UploadState.COMPLETE) - .forEach(this::deleteImageEntity); + .forEach(Image::markAsDeprecated); final List newImages = images.stream() .filter(image -> image.getUploadState() == UploadState.PENDING) .toList(); newImages.forEach(Image::uploadComplete); - final List imageUrls = newImages.stream() - .map(this::createImageUrl) - .toList(); - Product product = productUtil.findProductEntityById(productId); - product.updateImages(imageUrls); + + return newImages; } - private PutObjectRequest createPutObjectRequest( - final String fileName, - final FileExtension fileExtension - ) { + private PutObjectRequest createPutObjectRequest(final String fileName, final FileExtension fileExtension) { return PutObjectRequest.builder() .bucket(bucketName) .key(fileName) @@ -167,29 +140,14 @@ private PutObjectRequest createPutObjectRequest( .build(); } - private PutObjectPresignRequest createPutObjectPresignRequest( - final PutObjectRequest putObjectRequest - ) { + private PutObjectPresignRequest createPutObjectPresignRequest(final PutObjectRequest putObjectRequest) { return PutObjectPresignRequest.builder() .signatureDuration(SIGNATURE_DURATION) .putObjectRequest(putObjectRequest) .build(); } - private Image createImageEntity( - final ImageType imageType, - final Long targetId, - final String imageKey, - final FileExtension fileExtension - ) { - return Image.of(imageType, targetId, imageKey, fileExtension); - } - - private void deleteImageEntity(final Image image) { - imageRepository.delete(image); - } - private String createImageUrl(final Image image) { return "https://" + cloudFrontDomain + "/" + image.createFileName(); } -} +} \ No newline at end of file From 85719769d03e15dac9fc95e8524a7f743acef8aa Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 16:37:20 +0900 Subject: [PATCH 51/64] KL-119/fix: checkstyle error --- .../image/service/ImageServiceImpl.java | 20 ++++++++++++++----- .../domain/product/domain/ProductImage.java | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 6a935f8b..df058494 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -96,13 +96,18 @@ public void uploadCompleteProductImage(final Long productId) { product.updateImages(imageUrls); } - private PresignedUrlResponse createImageUploadUrl(ImageType imageType, Long targetId, String fileExtensionStr) { + private PresignedUrlResponse createImageUploadUrl( + final ImageType imageType, + final Long targetId, + final String fileExtensionStr + ) { final String imageKey = ImageKeyGenerator.generate(); final FileExtension fileExtension = FileExtension.from(fileExtensionStr); final Image image = createAndSaveImageEntity(imageType, targetId, imageKey, fileExtension); - final PutObjectRequest putObjectRequest = createPutObjectRequest(image.createFileName(), image.getFileExtension()); + final PutObjectRequest putObjectRequest + = createPutObjectRequest(image.createFileName(), image.getFileExtension()); final PutObjectPresignRequest putObjectPresignRequest = createPutObjectPresignRequest(putObjectRequest); final PresignedPutObjectRequest presignedRequest = s3Presigner.presignPutObject(putObjectPresignRequest); @@ -111,12 +116,17 @@ private PresignedUrlResponse createImageUploadUrl(ImageType imageType, Long targ return PresignedUrlResponse.from(presignedUrl); } - private Image createAndSaveImageEntity(ImageType imageType, Long targetId, String imageKey, FileExtension fileExtension) { + private Image createAndSaveImageEntity( + final ImageType imageType, + final Long targetId, + final String imageKey, + final FileExtension fileExtension + ) { final Image image = Image.of(imageType, targetId, imageKey, fileExtension); return imageRepository.save(image); } - private List uploadCompleteImage(ImageType imageType, Long targetId) { + private List uploadCompleteImage(final ImageType imageType, Long targetId) { final List images = imageRepository.findAllByImageTypeAndTargetId(imageType, targetId); images.stream() @@ -150,4 +160,4 @@ private PutObjectPresignRequest createPutObjectPresignRequest(final PutObjectReq private String createImageUrl(final Image image) { return "https://" + cloudFrontDomain + "/" + image.createFileName(); } -} \ No newline at end of file +} diff --git a/src/main/java/taco/klkl/domain/product/domain/ProductImage.java b/src/main/java/taco/klkl/domain/product/domain/ProductImage.java index f5077d1b..0a0e5222 100644 --- a/src/main/java/taco/klkl/domain/product/domain/ProductImage.java +++ b/src/main/java/taco/klkl/domain/product/domain/ProductImage.java @@ -39,7 +39,7 @@ public class ProductImage { @Column( name = "order_index", - nullable= false + nullable = false ) private Integer orderIndex; @@ -60,4 +60,4 @@ private ProductImage( this.imageUrl = imageUrl; this.orderIndex = orderIndex; } -} \ No newline at end of file +} From 7a8f1b26c9984e9a122073f95a1c776bf64d4ff4 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 16:39:05 +0900 Subject: [PATCH 52/64] KL-119/test: fix test errors --- .../comment/controller/CommentControllerTest.java | 2 -- .../domain/comment/service/CommentServiceTest.java | 2 -- .../service/NotificationServiceTest.java | 2 +- .../domain/user/dto/request/UserRequestDtoTest.java | 6 +++--- .../user/dto/response/UserDetailResponseTest.java | 4 +--- .../user/dto/response/UserSimpleResponseTest.java | 4 +--- .../domain/user/service/UserServiceImplTest.java | 13 ------------- 7 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java index a3fa3c25..39d3dc5a 100644 --- a/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java +++ b/src/test/java/taco/klkl/domain/comment/controller/CommentControllerTest.java @@ -68,11 +68,9 @@ public class CommentControllerTest { "이상화", "남", 19, - "image/ideal-flower.jpg", "저는 이상화입니다." ); private final User user = User.of( - requestDto.profileImageUrl(), requestDto.name(), Gender.from(requestDto.gender()), requestDto.age(), diff --git a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java index b1783f4a..19d63559 100644 --- a/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java +++ b/src/test/java/taco/klkl/domain/comment/service/CommentServiceTest.java @@ -52,12 +52,10 @@ public class CommentServiceTest { "이상화", "남", 19, - "image/ideal-flower.jpg", "저는 이상화입니다." ); private final User user = User.of( - userRequestDto.profileImageUrl(), userRequestDto.name(), Gender.from(userRequestDto.gender()), userRequestDto.age(), diff --git a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java index 8381fb28..fffc0895 100644 --- a/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java +++ b/src/test/java/taco/klkl/domain/notification/service/NotificationServiceTest.java @@ -76,7 +76,7 @@ public class NotificationServiceTest { @BeforeEach public void setUp() { - commentUser = User.of("profileImageUrl", + commentUser = User.of( "윤상정", Gender.FEMALE, 26, diff --git a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java index 43ebfe3e..599db180 100644 --- a/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/request/UserRequestDtoTest.java @@ -24,7 +24,7 @@ public UserRequestDtoTest() { @DisplayName("유효한 UserCreateRequestDto에 대한 유효성 검사") public void testValidUserCreateRequestDto() { // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", 20, "image/profileImageUrl.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest("이름", "남", 20, "자기소개"); // when Set> violations = validator.validate(requestDto); @@ -37,7 +37,7 @@ public void testValidUserCreateRequestDto() { @DisplayName("이름이 null인 UserCreateRequest 유효성 검사") public void testInvalidUserCreateRequestDto_NameRequired() { // given - UserCreateRequest requestDto = new UserCreateRequest(null, "남", 20, "image/profileImageUrl.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest(null, "남", 20, "자기소개"); // when Set> violations = validator.validate(requestDto); @@ -50,7 +50,7 @@ public void testInvalidUserCreateRequestDto_NameRequired() { @DisplayName("나이가 음수인 UserCreateRequest 유효성 검사") public void testInvalidUserCreateRequestDto_AgeNegative() { // given - UserCreateRequest requestDto = new UserCreateRequest("이름", "남", -1, "image/profileImageUrl.png", "자기소개"); + UserCreateRequest requestDto = new UserCreateRequest("이름", "남", -1, "자기소개"); // when Set> violations = validator.validate(requestDto); diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java index ed337378..957864c9 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserDetailResponseTest.java @@ -35,18 +35,16 @@ public void testUserDetailResponseDto() { @DisplayName("User 객체로부터 UserDetailResponse 생성 테스트") public void testFrom() { // given - String profile = "image/profileImageUrl.png"; String name = "이름"; Gender gender = Gender.MALE; int age = 20; String description = "자기소개"; // when - User user = User.of(profile, name, gender, age, description); + User user = User.of(name, gender, age, description); UserDetailResponse userDetail = UserDetailResponse.from(user); // then - assertThat(userDetail.profileImageUrl()).isEqualTo(profile); assertThat(userDetail.name()).isEqualTo(name); assertThat(userDetail.description()).isEqualTo(description); assertThat(userDetail.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); diff --git a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java index 841c149e..547f3509 100644 --- a/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java +++ b/src/test/java/taco/klkl/domain/user/dto/response/UserSimpleResponseTest.java @@ -30,18 +30,16 @@ public void testUserSimpleResponseDto() { @DisplayName("User 객체로부터 UserSimpleResponse 생성 테스트") public void testFrom() { // given - String profile = "image/profileImageUrl.png"; String name = "이름"; Gender gender = Gender.MALE; int age = 20; String description = "자기소개"; // when - User user = User.of(profile, name, gender, age, description); + User user = User.of(name, gender, age, description); UserSimpleResponse userSimple = UserSimpleResponse.from(user); // then - assertThat(userSimple.profileImageUrl()).isEqualTo(profile); assertThat(userSimple.name()).isEqualTo(name); } } diff --git a/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java index 46ab3a08..bed1370d 100644 --- a/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java +++ b/src/test/java/taco/klkl/domain/user/service/UserServiceImplTest.java @@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory; import taco.klkl.domain.image.domain.Image; -import taco.klkl.domain.image.domain.ImageType; import taco.klkl.domain.image.domain.UploadState; import taco.klkl.domain.user.dao.UserRepository; import taco.klkl.domain.user.domain.Gender; @@ -21,7 +20,6 @@ import taco.klkl.domain.user.dto.request.UserCreateRequest; import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.global.common.constants.UserConstants; -import taco.klkl.global.util.ImageUtil; import taco.klkl.global.util.UserUtil; @ExtendWith(MockitoExtension.class) @@ -34,9 +32,6 @@ class UserServiceImplTest { @Mock UserUtil userUtil; - @Mock - ImageUtil imageUtil; - @InjectMocks UserServiceImpl userServiceImpl; @@ -71,11 +66,9 @@ public void testCreateUser() { "이상화", "남", 19, - "image/ideal-flower.jpg", "저는 이상화입니다." ); User user = User.of( - requestDto.profileImageUrl(), requestDto.name(), Gender.from(requestDto.gender()), requestDto.age(), @@ -83,17 +76,11 @@ public void testCreateUser() { ); when(userRepository.save(any(User.class))).thenReturn(user); - // ImageUtil mock 설정 - Image mockImage = mock(Image.class); - when(mockImage.getUploadState()).thenReturn(UploadState.COMPLETE); - when(imageUtil.findImageByImageUrl(any(ImageType.class), anyString())).thenReturn(mockImage); - // when UserDetailResponse responseDto = userServiceImpl.createUser(requestDto); // then assertThat(responseDto.name()).isEqualTo(requestDto.name()); - assertThat(responseDto.profileImageUrl()).isEqualTo(requestDto.profileImageUrl()); assertThat(responseDto.description()).isEqualTo(requestDto.description()); assertThat(responseDto.totalLikeCount()).isEqualTo(UserConstants.DEFAULT_TOTAL_LIKE_COUNT); } From 84fdfc00e06847bcaa69779f1f55f3ed21bbb323 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 16:49:52 +0900 Subject: [PATCH 53/64] KL-119/refactor: unify image upload request dto --- .../klkl/domain/image/controller/ImageController.java | 7 +++---- ...ImageUploadRequest.java => ImageUploadRequest.java} | 2 +- .../image/dto/request/ProductImageUploadRequest.java | 10 ---------- .../taco/klkl/domain/image/service/ImageService.java | 7 +++---- .../klkl/domain/image/service/ImageServiceImpl.java | 7 +++---- 5 files changed, 10 insertions(+), 23 deletions(-) rename src/main/java/taco/klkl/domain/image/dto/request/{UserImageUploadRequest.java => ImageUploadRequest.java} (87%) delete mode 100644 src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java diff --git a/src/main/java/taco/klkl/domain/image/controller/ImageController.java b/src/main/java/taco/klkl/domain/image/controller/ImageController.java index 675f3433..ddca6a7e 100644 --- a/src/main/java/taco/klkl/domain/image/controller/ImageController.java +++ b/src/main/java/taco/klkl/domain/image/controller/ImageController.java @@ -11,8 +11,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; -import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.request.ImageUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.service.ImageService; @@ -30,7 +29,7 @@ public class ImageController { ) @PostMapping("/v1/users/me/upload-url") public PresignedUrlResponse createUserImageUploadUrl( - @Valid @RequestBody final UserImageUploadRequest request + @Valid @RequestBody final ImageUploadRequest request ) { return imageService.createUserImageUploadUrl(request); } @@ -52,7 +51,7 @@ public ResponseEntity uploadCompleteUserImage() { @PostMapping("/v1/products/{productId}/upload-url") public PresignedUrlResponse createProductImageUploadUrl( @PathVariable final Long productId, - @Valid @RequestBody final ProductImageUploadRequest request + @Valid @RequestBody final ImageUploadRequest request ) { return imageService.createProductImageUploadUrl(productId, request); } diff --git a/src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/ImageUploadRequest.java similarity index 87% rename from src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java rename to src/main/java/taco/klkl/domain/image/dto/request/ImageUploadRequest.java index 213a1ca7..c3fd5c79 100644 --- a/src/main/java/taco/klkl/domain/image/dto/request/UserImageUploadRequest.java +++ b/src/main/java/taco/klkl/domain/image/dto/request/ImageUploadRequest.java @@ -3,7 +3,7 @@ import jakarta.validation.constraints.NotBlank; import taco.klkl.global.common.constants.ImageValidationMessages; -public record UserImageUploadRequest( +public record ImageUploadRequest( @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) String fileExtension ) { diff --git a/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java b/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java deleted file mode 100644 index d737d927..00000000 --- a/src/main/java/taco/klkl/domain/image/dto/request/ProductImageUploadRequest.java +++ /dev/null @@ -1,10 +0,0 @@ -package taco.klkl.domain.image.dto.request; - -import jakarta.validation.constraints.NotBlank; -import taco.klkl.global.common.constants.ImageValidationMessages; - -public record ProductImageUploadRequest( - @NotBlank(message = ImageValidationMessages.FILE_EXTENSION_NOT_BLANK) - String fileExtension -) { -} diff --git a/src/main/java/taco/klkl/domain/image/service/ImageService.java b/src/main/java/taco/klkl/domain/image/service/ImageService.java index bffdbad2..7acd6ead 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageService.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageService.java @@ -2,20 +2,19 @@ import org.springframework.stereotype.Service; -import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; -import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.request.ImageUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; @Service public interface ImageService { - PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest createRequest); + PresignedUrlResponse createUserImageUploadUrl(final ImageUploadRequest createRequest); void uploadCompleteUserImage(); PresignedUrlResponse createProductImageUploadUrl( final Long productId, - final ProductImageUploadRequest uploadRequest + final ImageUploadRequest uploadRequest ); void uploadCompleteProductImage(final Long productId); diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index df058494..6aefe88c 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -21,8 +21,7 @@ import taco.klkl.domain.image.domain.Image; import taco.klkl.domain.image.domain.ImageType; import taco.klkl.domain.image.domain.UploadState; -import taco.klkl.domain.image.dto.request.ProductImageUploadRequest; -import taco.klkl.domain.image.dto.request.UserImageUploadRequest; +import taco.klkl.domain.image.dto.request.ImageUploadRequest; import taco.klkl.domain.image.dto.response.PresignedUrlResponse; import taco.klkl.domain.image.exception.ImageNotFoundException; import taco.klkl.domain.product.domain.Product; @@ -54,7 +53,7 @@ public class ImageServiceImpl implements ImageService { @Override @Transactional - public PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadRequest uploadRequest) { + public PresignedUrlResponse createUserImageUploadUrl(final ImageUploadRequest uploadRequest) { final User currentUser = userUtil.findCurrentUser(); return createImageUploadUrl(ImageType.USER_IMAGE, currentUser.getId(), uploadRequest.fileExtension()); } @@ -63,7 +62,7 @@ public PresignedUrlResponse createUserImageUploadUrl(final UserImageUploadReques @Transactional public PresignedUrlResponse createProductImageUploadUrl( final Long productId, - final ProductImageUploadRequest uploadRequest + final ImageUploadRequest uploadRequest ) { return createImageUploadUrl(ImageType.PRODUCT_IMAGE, productId, uploadRequest.fileExtension()); } From 29a9fa15f2761e4d257b7e7e48f8c723cd21150a Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 17:16:48 +0900 Subject: [PATCH 54/64] KL-119/feat: add ProductImageResponse --- .../klkl/domain/product/domain/Product.java | 8 ++++--- .../dto/response/ProductDetailResponse.java | 24 ++++++------------- .../dto/response/ProductImageResponse.java | 12 ++++++++++ .../dto/response/ProductSimpleResponse.java | 18 ++++++-------- 4 files changed, 31 insertions(+), 31 deletions(-) create mode 100644 src/main/java/taco/klkl/domain/product/dto/response/ProductImageResponse.java diff --git a/src/main/java/taco/klkl/domain/product/domain/Product.java b/src/main/java/taco/klkl/domain/product/domain/Product.java index 85581389..48c61e5a 100644 --- a/src/main/java/taco/klkl/domain/product/domain/Product.java +++ b/src/main/java/taco/klkl/domain/product/domain/Product.java @@ -233,9 +233,11 @@ public int decreaseLikeCount() throws LikeCountBelowMinimumException { public void updateImages(final List imageUrls) { this.images.clear(); - this.images = IntStream.range(0, imageUrls.size()) - .mapToObj(i -> ProductImage.of(this, imageUrls.get(i), i)) - .toList(); + IntStream.range(0, imageUrls.size()) + .forEach(i -> { + ProductImage newImage = ProductImage.of(this, imageUrls.get(i), i); + this.images.add(newImage); + }); } private Product( diff --git a/src/main/java/taco/klkl/domain/product/dto/response/ProductDetailResponse.java b/src/main/java/taco/klkl/domain/product/dto/response/ProductDetailResponse.java index d8648e92..a8236ca5 100644 --- a/src/main/java/taco/klkl/domain/product/dto/response/ProductDetailResponse.java +++ b/src/main/java/taco/klkl/domain/product/dto/response/ProductDetailResponse.java @@ -1,6 +1,7 @@ package taco.klkl.domain.product.dto.response; import java.time.LocalDateTime; +import java.util.List; import java.util.Set; import taco.klkl.domain.category.dto.response.SubcategoryResponse; @@ -11,24 +12,9 @@ import taco.klkl.domain.user.dto.response.UserDetailResponse; import taco.klkl.global.util.ProductUtil; -/** - * TODO: 상품필터속성 추가 해야함 (상품필터속성 테이블 개발 후) - * TODO: 상품 컨트롤러에서 필터 서비스를 이용해서 조합 하는게 괜찮아 보입니다. - * @param id - * @param name - * @param description - * @param address - * @param price - * @param likeCount - * @param rating - * @param user - * @param city - * @param subcategory - * @param currency - * @param createdAt - */ public record ProductDetailResponse( Long id, + List images, String name, String description, String address, @@ -42,10 +28,14 @@ public record ProductDetailResponse( Set tags, LocalDateTime createdAt ) { - public static ProductDetailResponse from(final Product product) { + List images = product.getImages().stream() + .map(ProductImageResponse::from) + .toList(); + return new ProductDetailResponse( product.getId(), + images, product.getName(), product.getDescription(), product.getAddress(), diff --git a/src/main/java/taco/klkl/domain/product/dto/response/ProductImageResponse.java b/src/main/java/taco/klkl/domain/product/dto/response/ProductImageResponse.java new file mode 100644 index 00000000..e682af1c --- /dev/null +++ b/src/main/java/taco/klkl/domain/product/dto/response/ProductImageResponse.java @@ -0,0 +1,12 @@ +package taco.klkl.domain.product.dto.response; + +import taco.klkl.domain.product.domain.ProductImage; + +public record ProductImageResponse( + String url, + Integer orderIndex +) { + public static ProductImageResponse from(final ProductImage productImage) { + return new ProductImageResponse(productImage.getImageUrl(), productImage.getOrderIndex()); + } +} diff --git a/src/main/java/taco/klkl/domain/product/dto/response/ProductSimpleResponse.java b/src/main/java/taco/klkl/domain/product/dto/response/ProductSimpleResponse.java index 8cd992e4..4ac17980 100644 --- a/src/main/java/taco/klkl/domain/product/dto/response/ProductSimpleResponse.java +++ b/src/main/java/taco/klkl/domain/product/dto/response/ProductSimpleResponse.java @@ -1,23 +1,15 @@ package taco.klkl.domain.product.dto.response; +import java.util.List; import java.util.Set; import taco.klkl.domain.category.dto.response.TagResponse; import taco.klkl.domain.product.domain.Product; import taco.klkl.global.util.ProductUtil; -/** - * TODO: 상품필터속성 추가 해야함 (상품필터속성 테이블 개발 후) - * TODO: 상품필터속성 추가 해야함 (상품필터속성 테이블 개발 후) - * @param id - * @param name - * @param likeCount - * @param rating - * @param countryName - * @param categoryName - */ public record ProductSimpleResponse( Long id, + List images, String name, Integer likeCount, Double rating, @@ -25,10 +17,14 @@ public record ProductSimpleResponse( String categoryName, Set tags ) { - public static ProductSimpleResponse from(final Product product) { + List images = product.getImages().stream() + .map(ProductImageResponse::from) + .toList(); + return new ProductSimpleResponse( product.getId(), + images, product.getName(), product.getLikeCount(), product.getRating().getValue(), From ebebec4be3b2dec4b6bdf87f22d50bed11f1329f Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 17:27:09 +0900 Subject: [PATCH 55/64] KL-119/test: fix test errors --- .../controller/ProductControllerTest.java | 8 +++++++ .../response/ProductSimpleResponseTest.java | 23 ++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java index 85dfaa22..79dea65f 100644 --- a/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java +++ b/src/test/java/taco/klkl/domain/product/controller/ProductControllerTest.java @@ -32,6 +32,7 @@ import taco.klkl.domain.product.dto.request.ProductFilterOptions; import taco.klkl.domain.product.dto.request.ProductSortOptions; import taco.klkl.domain.product.dto.response.ProductDetailResponse; +import taco.klkl.domain.product.dto.response.ProductImageResponse; import taco.klkl.domain.product.dto.response.ProductSimpleResponse; import taco.klkl.domain.product.service.ProductService; import taco.klkl.domain.region.domain.CountryType; @@ -87,8 +88,14 @@ void setUp() { "tagName2" ); + List imageResponses = List.of( + new ProductImageResponse("image/product1.jpg", 0), + new ProductImageResponse("image/product2.jpg", 1) + ); + productSimpleResponse = new ProductSimpleResponse( 1L, + imageResponses, "productName", 10, Rating.FIVE.getValue(), @@ -98,6 +105,7 @@ void setUp() { ); productDetailResponse = new ProductDetailResponse( 1L, + imageResponses, "productName", "Description", "123 Street", diff --git a/src/test/java/taco/klkl/domain/product/dto/response/ProductSimpleResponseTest.java b/src/test/java/taco/klkl/domain/product/dto/response/ProductSimpleResponseTest.java index 77418cc1..95e55a1d 100644 --- a/src/test/java/taco/klkl/domain/product/dto/response/ProductSimpleResponseTest.java +++ b/src/test/java/taco/klkl/domain/product/dto/response/ProductSimpleResponseTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -30,6 +31,7 @@ import taco.klkl.domain.region.domain.Region; import taco.klkl.domain.region.domain.RegionType; import taco.klkl.domain.user.domain.User; +import taco.klkl.global.util.ProductUtil; class ProductSimpleResponseTest { @@ -109,28 +111,33 @@ void testFromProduct() { @Test @DisplayName("생성자를 통해 ProductSimpleResponse 생성 테스트") void testConstructor() { - // when - Set filters = product.getProductTags().stream() - .map(ProductTag::getTag) - .map(TagResponse::from) - .collect(Collectors.toSet()); + // given + List images = product.getImages().stream() + .map(ProductImageResponse::from) + .toList(); + + Set tags = ProductUtil.createTagsByProduct(product); + // when ProductSimpleResponse dto = new ProductSimpleResponse( product.getId(), + images, product.getName(), product.getLikeCount(), product.getRating().getValue(), - city.getCountry().getName().getKoreanName(), + product.getCity().getCountry().getName().getKoreanName(), product.getSubcategory().getCategory().getName().getKoreanName(), - filters + tags ); // then assertThat(dto.id()).isEqualTo(product.getId()); + assertThat(dto.images()).isEqualTo(images); assertThat(dto.name()).isEqualTo(product.getName()); assertThat(dto.likeCount()).isEqualTo(product.getLikeCount()); assertThat(dto.rating()).isEqualTo(product.getRating().getValue()); - assertThat(dto.countryName()).isEqualTo(city.getCountry().getName().getKoreanName()); + assertThat(dto.countryName()).isEqualTo(product.getCity().getCountry().getName().getKoreanName()); assertThat(dto.categoryName()).isEqualTo(product.getSubcategory().getCategory().getName().getKoreanName()); + assertThat(dto.tags()).isEqualTo(tags); } } From 27c5c2338187710bfed612927fbe737515100974 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 18:05:06 +0900 Subject: [PATCH 56/64] KL-119/chore: edit workflow secret env logic --- .github/workflows/makefile.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/makefile.yaml b/.github/workflows/makefile.yaml index 754a7065..43c597ab 100644 --- a/.github/workflows/makefile.yaml +++ b/.github/workflows/makefile.yaml @@ -40,9 +40,9 @@ jobs: - name: Create .env file run: | - jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env - env: - SECRETS_CONTEXT: ${{ toJson(secrets) }} + touch ./env + echo "${{ secrets.ENV }}" > ./.env + shell: bash - name: Set up Gradle uses: gradle/actions/setup-gradle@v3 From f20d7929d84bc84441dcb7ede15423ac3f1a0568 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 18:48:38 +0900 Subject: [PATCH 57/64] KL-119/fix: add spring profile active for test --- .github/workflows/makefile.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/makefile.yaml b/.github/workflows/makefile.yaml index 43c597ab..4ceb7e64 100644 --- a/.github/workflows/makefile.yaml +++ b/.github/workflows/makefile.yaml @@ -53,7 +53,9 @@ jobs: run: make build - name: Run test code + env: + SPRING_PROFILES_ACTIVE: test run: make test - name: Run docker - run: make up + run: make up \ No newline at end of file From 0d8ef596d6e63c54d6dc490258fe8a698cb569f7 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:07:37 +0900 Subject: [PATCH 58/64] KL-119/fix: restore workflow and add test system property --- .github/workflows/makefile.yaml | 10 ++++------ build.gradle | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/makefile.yaml b/.github/workflows/makefile.yaml index 4ceb7e64..754a7065 100644 --- a/.github/workflows/makefile.yaml +++ b/.github/workflows/makefile.yaml @@ -40,9 +40,9 @@ jobs: - name: Create .env file run: | - touch ./env - echo "${{ secrets.ENV }}" > ./.env - shell: bash + jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env + env: + SECRETS_CONTEXT: ${{ toJson(secrets) }} - name: Set up Gradle uses: gradle/actions/setup-gradle@v3 @@ -53,9 +53,7 @@ jobs: run: make build - name: Run test code - env: - SPRING_PROFILES_ACTIVE: test run: make test - name: Run docker - run: make up \ No newline at end of file + run: make up diff --git a/build.gradle b/build.gradle index acc3f369..8cd35c83 100644 --- a/build.gradle +++ b/build.gradle @@ -74,4 +74,5 @@ test { useJUnitPlatform() dependsOn 'checkstyleMain' dependsOn 'checkstyleTest' + systemProperty 'spring.profiles.active', 'test' } From dfe2726e70c72b6bdd3cb578d7ea3c02ff672f57 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:08:37 +0900 Subject: [PATCH 59/64] KL-119/chore: remove comment --- .../klkl/domain/user/service/UserServiceImpl.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java index f3ec9827..537732fb 100644 --- a/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java +++ b/src/main/java/taco/klkl/domain/user/service/UserServiceImpl.java @@ -42,18 +42,6 @@ public User createUser(final UserCreateRequest createRequest) { return userRepository.save(user); } - // public User createUser(UserCreateRequest userDto) { - // final User user = User.of( - // userDto.profile(), - // userDto.name(), - // Gender.getGenderByDescription(userDto.description()), - // userDto.age(), - // userDto.description() - // ); - // - // return userRepository.save(user); - // } - @Override @Transactional public UserDetailResponse updateUser(final UserUpdateRequest updateRequest) { From 8da39c3ed9a3e51ca84189152be879f747b971fd Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:12:28 +0900 Subject: [PATCH 60/64] KL-119/chore: edit workflow secret env logic --- .github/workflows/makefile.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/makefile.yaml b/.github/workflows/makefile.yaml index 754a7065..43c597ab 100644 --- a/.github/workflows/makefile.yaml +++ b/.github/workflows/makefile.yaml @@ -40,9 +40,9 @@ jobs: - name: Create .env file run: | - jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' <<< "$SECRETS_CONTEXT" > .env - env: - SECRETS_CONTEXT: ${{ toJson(secrets) }} + touch ./env + echo "${{ secrets.ENV }}" > ./.env + shell: bash - name: Set up Gradle uses: gradle/actions/setup-gradle@v3 From 9cf53b3f3cd2fac937cbdd7021420385b044cc8c Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:41:18 +0900 Subject: [PATCH 61/64] KL-119/chore: add newline --- src/main/resources/application-storage.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application-storage.yaml b/src/main/resources/application-storage.yaml index 307a1eea..e9ddb373 100644 --- a/src/main/resources/application-storage.yaml +++ b/src/main/resources/application-storage.yaml @@ -17,4 +17,4 @@ cloud: s3: bucket: ${S3_BUCKET_NAME} cloudfront: - domain: ${S3_CLOUDFRONT_DOMAIN} \ No newline at end of file + domain: ${S3_CLOUDFRONT_DOMAIN} From 4313c5cbc9e8dc6c70710942f24f6b65a3cd3d2e Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:41:48 +0900 Subject: [PATCH 62/64] KL-119/fix: fix uninteded logic in enum --- src/main/java/taco/klkl/domain/image/domain/FileExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java index c1d2c441..15ba7ff9 100644 --- a/src/main/java/taco/klkl/domain/image/domain/FileExtension.java +++ b/src/main/java/taco/klkl/domain/image/domain/FileExtension.java @@ -19,7 +19,7 @@ public enum FileExtension { public static FileExtension from(final String fileExtension) throws FileExtensionNotFoundException { return Arrays.stream(FileExtension.values()) - .filter(extension -> extension.toString().equals(fileExtension)) + .filter(extension -> extension.getValue().equals(fileExtension)) .findFirst() .orElseThrow(FileExtensionNotFoundException::new); } From 576e64039646212805a1b68348c2cb4f3176a226 Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:42:06 +0900 Subject: [PATCH 63/64] KL-119/chore: remove unused methods --- src/main/java/taco/klkl/global/util/UserUtil.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/taco/klkl/global/util/UserUtil.java b/src/main/java/taco/klkl/global/util/UserUtil.java index 3b992eb8..78384255 100644 --- a/src/main/java/taco/klkl/global/util/UserUtil.java +++ b/src/main/java/taco/klkl/global/util/UserUtil.java @@ -33,15 +33,6 @@ public User findCurrentUser() { return findTestUser(); } - public User findUserById(final Long id) { - return userRepository.findById(id) - .orElseThrow(UserNotFoundException::new); - } - - public User findUserByName(final String name) { - return userRepository.findFirstByName(name); - } - public String createUsername(final String name, final Long oauthMemberId) { String createdName = generateUsername(name, oauthMemberId); From 0073c8fec3ac186ad63c8ce7bb92f3068b8ee12b Mon Sep 17 00:00:00 2001 From: ohhamma Date: Wed, 28 Aug 2024 19:47:12 +0900 Subject: [PATCH 64/64] KL-119/chore: rename as OUTDATED --- src/main/java/taco/klkl/domain/image/domain/Image.java | 4 ++-- src/main/java/taco/klkl/domain/image/domain/UploadState.java | 2 +- .../java/taco/klkl/domain/image/service/ImageServiceImpl.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/taco/klkl/domain/image/domain/Image.java b/src/main/java/taco/klkl/domain/image/domain/Image.java index 3d2c04d5..0a0e4338 100644 --- a/src/main/java/taco/klkl/domain/image/domain/Image.java +++ b/src/main/java/taco/klkl/domain/image/domain/Image.java @@ -92,8 +92,8 @@ public void uploadComplete() { this.uploadState = UploadState.COMPLETE; } - public void markAsDeprecated() { - this.uploadState = UploadState.DEPRECATED; + public void markAsOutdated() { + this.uploadState = UploadState.OUTDATED; } public String createFileName() { diff --git a/src/main/java/taco/klkl/domain/image/domain/UploadState.java b/src/main/java/taco/klkl/domain/image/domain/UploadState.java index 06aaba1e..ccc9a1ce 100644 --- a/src/main/java/taco/klkl/domain/image/domain/UploadState.java +++ b/src/main/java/taco/klkl/domain/image/domain/UploadState.java @@ -11,7 +11,7 @@ public enum UploadState { PENDING("대기중"), COMPLETE("완료"), - DEPRECATED("폐기예정"), + OUTDATED("폐기예정"), ; private final String value; diff --git a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java index 6aefe88c..cb3feb2b 100644 --- a/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java +++ b/src/main/java/taco/klkl/domain/image/service/ImageServiceImpl.java @@ -130,7 +130,7 @@ private List uploadCompleteImage(final ImageType imageType, Long targetId images.stream() .filter(image -> image.getUploadState() == UploadState.COMPLETE) - .forEach(Image::markAsDeprecated); + .forEach(Image::markAsOutdated); final List newImages = images.stream() .filter(image -> image.getUploadState() == UploadState.PENDING)