From 178f990ed1268e66bfbf7d81b9b307166711a309 Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 23:06:01 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat=20:=20s3=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B5=AC=ED=98=84=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../com/get_offer/common/s3/S3Config.kt | 28 +++++++++ .../get_offer/common/s3/S3FileManagement.kt | 63 +++++++++++++++++++ .../com/get_offer/multipart/FileValidate.kt | 20 ++++++ .../com/get_offer/multipart/ImageService.kt | 18 ++++++ .../product/controller/ProductController.kt | 18 +++++- src/main/resources/application.yml | 10 ++- 7 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/com/get_offer/common/s3/S3Config.kt create mode 100644 src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt create mode 100644 src/main/kotlin/com/get_offer/multipart/FileValidate.kt create mode 100644 src/main/kotlin/com/get_offer/multipart/ImageService.kt diff --git a/build.gradle b/build.gradle index 9e195eb..855c650 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.fasterxml.jackson.module:jackson-module-kotlin' implementation 'org.jetbrains.kotlin:kotlin-reflect' + implementation("com.amazonaws:aws-java-sdk-s3:1.12.773") compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' diff --git a/src/main/kotlin/com/get_offer/common/s3/S3Config.kt b/src/main/kotlin/com/get_offer/common/s3/S3Config.kt new file mode 100644 index 0000000..b28d6b0 --- /dev/null +++ b/src/main/kotlin/com/get_offer/common/s3/S3Config.kt @@ -0,0 +1,28 @@ +package com.get_offer.common.s3 + +import com.amazonaws.auth.AWSStaticCredentialsProvider +import com.amazonaws.auth.BasicAWSCredentials +import com.amazonaws.regions.Regions +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.AmazonS3ClientBuilder +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class S3Config( + @Value("\${aws.credentials.accessKey}") + private val accessKey: String, + @Value("\${aws.credentials.secretKey}") + private val secretKey: String, +) { + @Bean + fun amazonS3Client(): AmazonS3 { + val credentials = BasicAWSCredentials(accessKey, secretKey) + return AmazonS3ClientBuilder + .standard() + .withCredentials(AWSStaticCredentialsProvider(credentials)) + .withRegion(Regions.AP_NORTHEAST_2) + .build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt b/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt new file mode 100644 index 0000000..b814fec --- /dev/null +++ b/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt @@ -0,0 +1,63 @@ +package com.get_offer.common.s3 + +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.model.ObjectMetadata +import com.get_offer.common.exception.NotFoundException +import com.get_offer.multipart.FileValidate +import java.util.* +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import org.springframework.web.multipart.MultipartFile + +@Component +class S3FileManagement( + @Value("\${aws.s3.bucket}") + private val bucket: String, + private val amazonS3: AmazonS3, +) { + companion object { + const val TYPE_IMAGE = "image" + } + + fun uploadImages(multipartFiles: List): List { + return multipartFiles.map { uploadImage(it) } + } + + fun uploadImage(multipartFile: MultipartFile): String { + val originalFilename = multipartFile.originalFilename + ?: throw NotFoundException("") + FileValidate.checkImageFormat(originalFilename) + val fileName = "${UUID.randomUUID()}-${originalFilename}" + val objectMetadata = setFileDateOption( + type = TYPE_IMAGE, + file = getFileExtension(originalFilename), + multipartFile = multipartFile + ) + amazonS3.putObject(bucket, fileName, multipartFile.inputStream, objectMetadata) + return fileName + } + + fun getFile(fileName: String): String { + return amazonS3.getUrl(bucket, fileName).toString() + } + + fun delete(fileName: String) { + amazonS3.deleteObject(bucket, fileName) + } + + private fun getFileExtension(fileName: String): String { + val extensionIndex = fileName.lastIndexOf('.') + return fileName.substring(extensionIndex + 1) + } + + private fun setFileDateOption( + type: String, + file: String, + multipartFile: MultipartFile + ): ObjectMetadata { + val objectMetadata = ObjectMetadata() + objectMetadata.contentType = "/${type}/${getFileExtension(file)}" + objectMetadata.contentLength = multipartFile.inputStream.available().toLong() + return objectMetadata + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/multipart/FileValidate.kt b/src/main/kotlin/com/get_offer/multipart/FileValidate.kt new file mode 100644 index 0000000..af2bbe0 --- /dev/null +++ b/src/main/kotlin/com/get_offer/multipart/FileValidate.kt @@ -0,0 +1,20 @@ +package com.get_offer.multipart + +import com.get_offer.common.exception.NotFoundException + +class FileValidate { + companion object { + private val IMAGE_EXTENSIONS: List = listOf("jpg", "png") + + fun checkImageFormat(fileName: String) { + val extensionIndex = fileName.lastIndexOf('.') + if (extensionIndex == -1) { + throw NotFoundException("") // Not exists file extension + } + val extension = fileName.substring(extensionIndex + 1) + require(IMAGE_EXTENSIONS.contains(extension)) { + throw NotFoundException("") // Not exists file extension + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/multipart/ImageService.kt b/src/main/kotlin/com/get_offer/multipart/ImageService.kt new file mode 100644 index 0000000..4b8da74 --- /dev/null +++ b/src/main/kotlin/com/get_offer/multipart/ImageService.kt @@ -0,0 +1,18 @@ +package com.get_offer.multipart + +import com.get_offer.common.s3.S3FileManagement +import org.springframework.stereotype.Service +import org.springframework.web.multipart.MultipartFile + +@Service +class ImageService( + private val s3FileManagement: S3FileManagement, +) { + fun saveImages(images: List): List { + return s3FileManagement.uploadImages(images) + } + + fun deleteImage(imageUrl: String) { + val file = s3FileManagement.delete(imageUrl) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt index 3e1da6b..af83430 100644 --- a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt +++ b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt @@ -1,20 +1,25 @@ package com.get_offer.product.controller import ApiResponse +import com.get_offer.multipart.ImageService import com.get_offer.product.service.ProductDetailDto import com.get_offer.product.service.ProductListDto import com.get_offer.product.service.ProductService import org.springframework.data.domain.PageRequest import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RequestPart import org.springframework.web.bind.annotation.RestController +import org.springframework.web.multipart.MultipartFile @RestController @RequestMapping("/products") class ProductController( - private val productService: ProductService + private val productService: ProductService, + private val imageService: ImageService, ) { @GetMapping fun getProductList( @@ -33,4 +38,15 @@ class ProductController( fun getProductDetail(@PathVariable id: String, @RequestParam userId: String): ApiResponse { return ApiResponse.success(productService.getProductDetail(id.toLong(), userId.toLong())) } + + @PostMapping + fun postProduct( + @RequestParam userId: String, @RequestPart images: List + ) { + try { + imageService.saveImages(images) + } catch (e: Exception) { + throw e + } + } } \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 7c5dfc7..d08d5ec 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -22,4 +22,12 @@ spring: hibernate: dialect: org.hibernate.dialect.H2Dialect format_sql: true - show_sql: true \ No newline at end of file + show_sql: true +aws: + s3: + bucket: get-offer-bucket + stack: + auto: false + credentials: + accessKey: + secretKey: \ No newline at end of file From 8cfd92a6adb754692bc2b27a998a8f3264f904b8 Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 23:06:29 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat=20:=20s3=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B5=AC=ED=98=84=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d08d5ec..89e2946 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -28,6 +28,4 @@ aws: bucket: get-offer-bucket stack: auto: false - credentials: - accessKey: - secretKey: \ No newline at end of file + credentials: \ No newline at end of file From efe5464aa4b9a2fea82b38417833c528799c3724 Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 14:02:17 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=20=EC=98=AC?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=20=EA=B5=AC=ED=98=84=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../get_offer/common/AuditingTimeEntity.kt | 4 +- .../product/controller/ProductController.kt | 15 ++--- .../product/controller/ProductPostReqDto.kt | 13 +++++ .../product/service/ProductSaveDto.kt | 22 ++++++++ .../product/service/ProductService.kt | 56 ++++++++++++++++++- 5 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/main/kotlin/com/get_offer/product/controller/ProductPostReqDto.kt create mode 100644 src/main/kotlin/com/get_offer/product/service/ProductSaveDto.kt diff --git a/src/main/kotlin/com/get_offer/common/AuditingTimeEntity.kt b/src/main/kotlin/com/get_offer/common/AuditingTimeEntity.kt index d348be2..22a0247 100644 --- a/src/main/kotlin/com/get_offer/common/AuditingTimeEntity.kt +++ b/src/main/kotlin/com/get_offer/common/AuditingTimeEntity.kt @@ -11,8 +11,8 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener @MappedSuperclass abstract class AuditingTimeEntity { @CreatedDate - private var createdAt: LocalDateTime = LocalDateTime.now() + val createdAt: LocalDateTime = LocalDateTime.now() @LastModifiedDate - private var updatedAt: LocalDateTime = LocalDateTime.now() + var updatedAt: LocalDateTime = LocalDateTime.now() } \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt index af83430..f17663d 100644 --- a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt +++ b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt @@ -1,9 +1,9 @@ package com.get_offer.product.controller import ApiResponse -import com.get_offer.multipart.ImageService import com.get_offer.product.service.ProductDetailDto import com.get_offer.product.service.ProductListDto +import com.get_offer.product.service.ProductSaveDto import com.get_offer.product.service.ProductService import org.springframework.data.domain.PageRequest import org.springframework.web.bind.annotation.GetMapping @@ -19,7 +19,6 @@ import org.springframework.web.multipart.MultipartFile @RequestMapping("/products") class ProductController( private val productService: ProductService, - private val imageService: ImageService, ) { @GetMapping fun getProductList( @@ -41,12 +40,10 @@ class ProductController( @PostMapping fun postProduct( - @RequestParam userId: String, @RequestPart images: List - ) { - try { - imageService.saveImages(images) - } catch (e: Exception) { - throw e - } + @RequestParam userId: String, + @RequestPart("images") images: List, + @RequestPart productReqDto: ProductPostReqDto + ): ProductSaveDto { + return productService.postProduct(productReqDto, userId.toLong(), images) } } \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/controller/ProductPostReqDto.kt b/src/main/kotlin/com/get_offer/product/controller/ProductPostReqDto.kt new file mode 100644 index 0000000..58f5f6e --- /dev/null +++ b/src/main/kotlin/com/get_offer/product/controller/ProductPostReqDto.kt @@ -0,0 +1,13 @@ +package com.get_offer.product.controller + +import com.get_offer.product.domain.Category +import java.time.LocalDateTime + +class ProductPostReqDto( + val title: String, + val category: Category, + val description: String, + val startPrice: Int, + val startDate: LocalDateTime, + val endDate: LocalDateTime, +) \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/service/ProductSaveDto.kt b/src/main/kotlin/com/get_offer/product/service/ProductSaveDto.kt new file mode 100644 index 0000000..2a5d412 --- /dev/null +++ b/src/main/kotlin/com/get_offer/product/service/ProductSaveDto.kt @@ -0,0 +1,22 @@ +package com.get_offer.product.service + +import com.get_offer.product.domain.Product +import java.time.LocalDateTime + +data class ProductSaveDto( + val title: String, + val id: Long, + val writerId: Long, + val createdTime: LocalDateTime +) { + companion object { + fun of(product: Product): ProductSaveDto { + return ProductSaveDto( + title = product.title, + id = product.id, + writerId = product.writerId, + createdTime = product.createdAt + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/service/ProductService.kt b/src/main/kotlin/com/get_offer/product/service/ProductService.kt index 11b3ff5..6abe000 100644 --- a/src/main/kotlin/com/get_offer/product/service/ProductService.kt +++ b/src/main/kotlin/com/get_offer/product/service/ProductService.kt @@ -1,20 +1,29 @@ package com.get_offer.product.service import com.get_offer.common.exception.NotFoundException +import com.get_offer.multipart.ImageService +import com.get_offer.product.controller.ProductPostReqDto import com.get_offer.product.domain.Product +import com.get_offer.product.domain.ProductImagesVo import com.get_offer.product.domain.ProductStatus import com.get_offer.product.repository.ProductRepository import com.get_offer.user.repository.UserRepository +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit +import org.apache.coyote.BadRequestException import org.springframework.data.domain.Page import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.multipart.MultipartFile @Service +@Transactional(readOnly = true) class ProductService( + private val imageService: ImageService, private val productRepository: ProductRepository, private val userRepository: UserRepository, - - ) { +) { fun getProductList(userId: Long, pageRequest: PageRequest): Page { val productList: Page = productRepository.findAllByStatusInOrderByEndDateDesc( @@ -30,4 +39,47 @@ class ProductService( return ProductDetailDto.of(product, writer, userId) } + + @Transactional + fun postProduct(req: ProductPostReqDto, userId: Long, images: List): ProductSaveDto { + + validateStartPrice(req.startPrice) + validateDateRange(req.startDate, req.endDate) + + val imageUrls = imageService.saveImages(images) + + val product = productRepository.save( + Product( + title = req.title, + category = req.category, + writerId = userId, + images = ProductImagesVo(imageUrls), + description = req.description, + startPrice = req.startPrice, + currentPrice = req.startPrice, + startDate = req.startDate, + endDate = req.endDate, + status = checkStatus(req.startDate) + ) + ) + return ProductSaveDto.of(product) + } + + private fun validateStartPrice(startPrice: Int) { + if (startPrice < 0) { + throw BadRequestException("startPrice가 0보다 작을 수 없습니다.") + } + } + + private fun validateDateRange(startDate: LocalDateTime, endDate: LocalDateTime) { + if (startDate.isAfter(endDate)) throw BadRequestException() + if (ChronoUnit.DAYS.between(startDate, endDate) > 7) throw BadRequestException() + } + + private fun checkStatus(startDate: LocalDateTime): ProductStatus { + if (startDate.isAfter(LocalDateTime.now())) { + return ProductStatus.IN_PROGRESS + } + return ProductStatus.WAIT + } } \ No newline at end of file From 0f1f8878a10cdf29278835d7a9ed1922f6521f12 Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 14:37:20 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat=20:=20=EC=97=90=EB=9F=AC=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=88=98=EC=A0=95=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/get_offer/common/exception/CustomExceptions.kt | 4 +++- .../get_offer/common/exception/ExceptionControllerAdvice.kt | 5 +++++ src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt | 3 +-- src/main/kotlin/com/get_offer/multipart/FileValidate.kt | 6 +++--- .../kotlin/com/get_offer/product/service/ProductService.kt | 4 ++-- .../get_offer/product/controller/ProductIntegrationTest.kt | 5 +++++ 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/get_offer/common/exception/CustomExceptions.kt b/src/main/kotlin/com/get_offer/common/exception/CustomExceptions.kt index 60533e0..544428e 100644 --- a/src/main/kotlin/com/get_offer/common/exception/CustomExceptions.kt +++ b/src/main/kotlin/com/get_offer/common/exception/CustomExceptions.kt @@ -4,4 +4,6 @@ package com.get_offer.common.exception * custom exception 처리를 위해 남겨 놓았습니다. */ class NotFoundException(override val message: String) : RuntimeException(message) -class UnAuthorizationException : RuntimeException() \ No newline at end of file +class UnAuthorizationException : RuntimeException() + +class UnsupportedFileExtensionException : RuntimeException() \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/common/exception/ExceptionControllerAdvice.kt b/src/main/kotlin/com/get_offer/common/exception/ExceptionControllerAdvice.kt index ef1e911..70bb316 100644 --- a/src/main/kotlin/com/get_offer/common/exception/ExceptionControllerAdvice.kt +++ b/src/main/kotlin/com/get_offer/common/exception/ExceptionControllerAdvice.kt @@ -22,4 +22,9 @@ class ExceptionControllerAdvice { fun handleUnAuthorizationException(ex: UnAuthorizationException): ResponseEntity> { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error("인가되지 않은 사용자입니다")) } + + @ExceptionHandler + fun handleUnsupportedFileExtensionException(ex: UnsupportedFileExtensionException): ResponseEntity> { + return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).body(ApiResponse.error("파일의 확장자가 유효하지 않습니다.")) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt b/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt index b814fec..19f5a60 100644 --- a/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt +++ b/src/main/kotlin/com/get_offer/common/s3/S3FileManagement.kt @@ -2,7 +2,6 @@ package com.get_offer.common.s3 import com.amazonaws.services.s3.AmazonS3 import com.amazonaws.services.s3.model.ObjectMetadata -import com.get_offer.common.exception.NotFoundException import com.get_offer.multipart.FileValidate import java.util.* import org.springframework.beans.factory.annotation.Value @@ -25,7 +24,7 @@ class S3FileManagement( fun uploadImage(multipartFile: MultipartFile): String { val originalFilename = multipartFile.originalFilename - ?: throw NotFoundException("") + ?: throw IllegalStateException() FileValidate.checkImageFormat(originalFilename) val fileName = "${UUID.randomUUID()}-${originalFilename}" val objectMetadata = setFileDateOption( diff --git a/src/main/kotlin/com/get_offer/multipart/FileValidate.kt b/src/main/kotlin/com/get_offer/multipart/FileValidate.kt index af2bbe0..27d1af0 100644 --- a/src/main/kotlin/com/get_offer/multipart/FileValidate.kt +++ b/src/main/kotlin/com/get_offer/multipart/FileValidate.kt @@ -1,6 +1,6 @@ package com.get_offer.multipart -import com.get_offer.common.exception.NotFoundException +import com.get_offer.common.exception.UnsupportedFileExtensionException class FileValidate { companion object { @@ -9,11 +9,11 @@ class FileValidate { fun checkImageFormat(fileName: String) { val extensionIndex = fileName.lastIndexOf('.') if (extensionIndex == -1) { - throw NotFoundException("") // Not exists file extension + throw UnsupportedFileExtensionException() } val extension = fileName.substring(extensionIndex + 1) require(IMAGE_EXTENSIONS.contains(extension)) { - throw NotFoundException("") // Not exists file extension + throw UnsupportedFileExtensionException() } } } diff --git a/src/main/kotlin/com/get_offer/product/service/ProductService.kt b/src/main/kotlin/com/get_offer/product/service/ProductService.kt index 6abe000..b1cc038 100644 --- a/src/main/kotlin/com/get_offer/product/service/ProductService.kt +++ b/src/main/kotlin/com/get_offer/product/service/ProductService.kt @@ -72,8 +72,8 @@ class ProductService( } private fun validateDateRange(startDate: LocalDateTime, endDate: LocalDateTime) { - if (startDate.isAfter(endDate)) throw BadRequestException() - if (ChronoUnit.DAYS.between(startDate, endDate) > 7) throw BadRequestException() + if (startDate.isAfter(endDate)) throw BadRequestException("시작 날짜가 유효하지 않습니다.") + if (ChronoUnit.DAYS.between(startDate, endDate) > 7) throw BadRequestException("경매 기간은 7일을 넘길 수 없습니다.") } private fun checkStatus(startDate: LocalDateTime): ProductStatus { diff --git a/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt b/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt index bf40769..f0d12d9 100644 --- a/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt +++ b/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt @@ -64,4 +64,9 @@ class ProductIntegrationTest( .andExpect(jsonPath("$.data.endDate").value("2024-01-04T00:00:00")) .andExpect(jsonPath("$.data.isMine").value("true")) } + + @Test + fun postProductIntegrationTest() { + + } } \ No newline at end of file From c2a6a5083b0146234f757d52631709d1f720acee Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 20:47:37 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=ED=86=B5=ED=95=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/controller/ProductController.kt | 4 +- .../com/get_offer/product/domain/Product.kt | 11 ++-- .../controller/ProductIntegrationTest.kt | 57 +++++++++++++----- .../product/service/ProductServiceTest.kt | 8 ++- src/test/resources/img.png | Bin 0 -> 79172 bytes src/test/resources/test_data.sql | 1 + 6 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 src/test/resources/img.png diff --git a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt index f17663d..c44f8db 100644 --- a/src/main/kotlin/com/get_offer/product/controller/ProductController.kt +++ b/src/main/kotlin/com/get_offer/product/controller/ProductController.kt @@ -43,7 +43,7 @@ class ProductController( @RequestParam userId: String, @RequestPart("images") images: List, @RequestPart productReqDto: ProductPostReqDto - ): ProductSaveDto { - return productService.postProduct(productReqDto, userId.toLong(), images) + ): ApiResponse { + return ApiResponse.success(productService.postProduct(productReqDto, userId.toLong(), images)) } } \ No newline at end of file diff --git a/src/main/kotlin/com/get_offer/product/domain/Product.kt b/src/main/kotlin/com/get_offer/product/domain/Product.kt index 11cf6fb..cb3093a 100644 --- a/src/main/kotlin/com/get_offer/product/domain/Product.kt +++ b/src/main/kotlin/com/get_offer/product/domain/Product.kt @@ -19,11 +19,9 @@ class Product( val title: String, - @Enumerated(EnumType.STRING) - val category: Category, + @Enumerated(EnumType.STRING) val category: Category, - @Convert(converter = ProductImagesConverter::class) - @Column(name = "IMAGES") + @Convert(converter = ProductImagesConverter::class) @Column(name = "IMAGES") val images: ProductImagesVo, val description: String, @@ -39,7 +37,6 @@ class Product( var endDate: LocalDateTime, - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long = 0L, + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long = 0L, ) : AuditingTimeEntity() \ No newline at end of file diff --git a/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt b/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt index f0d12d9..c4b714a 100644 --- a/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt +++ b/src/test/kotlin/com/get_offer/product/controller/ProductIntegrationTest.kt @@ -1,11 +1,16 @@ package com.get_offer.product.controller +import java.nio.file.Files +import java.nio.file.Paths import org.junit.jupiter.api.Test import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest +import org.springframework.http.MediaType +import org.springframework.mock.web.MockMultipartFile import org.springframework.test.context.jdbc.Sql import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get import org.springframework.test.web.servlet.result.MockMvcResultHandlers import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath @@ -21,15 +26,10 @@ class ProductIntegrationTest( @Test fun productListIntegrationTest() { mockMvc.perform( - get("/products") - .param("userId", "1") - .param("page", "0") - .param("size", "30") + get("/products").param("userId", "1").param("page", "0").param("size", "30") ).andDo(MockMvcResultHandlers.print()).andExpect(status().isOk) - .andExpect(jsonPath("$.data.pageNumber").value(0)) - .andExpect(jsonPath("$.data.pageSize").value(30)) - .andExpect(jsonPath("$.data.totalElements").value(2)) - .andExpect(jsonPath("$.data.totalPages").value(1)) + .andExpect(jsonPath("$.data.pageNumber").value(0)).andExpect(jsonPath("$.data.pageSize").value(30)) + .andExpect(jsonPath("$.data.totalElements").value(2)).andExpect(jsonPath("$.data.totalPages").value(1)) .andExpect(jsonPath("$.data.content[0].id").value("1")) .andExpect(jsonPath("$.data.content[0].writerId").value("1")) .andExpect(jsonPath("$.data.content[0].name").value("nintendo")) @@ -46,14 +46,11 @@ class ProductIntegrationTest( fun productDetailIntegrationTest() { mockMvc.perform( get("/products/1/detail").param("userId", "1") - ).andDo(MockMvcResultHandlers.print()).andExpect(status().isOk) - .andExpect(jsonPath("$.data.id").value("1")) - .andExpect(jsonPath("$.data.name").value("nintendo")) - .andExpect(jsonPath("$.data.writer.id").value("1")) + ).andDo(MockMvcResultHandlers.print()).andExpect(status().isOk).andExpect(jsonPath("$.data.id").value("1")) + .andExpect(jsonPath("$.data.name").value("nintendo")).andExpect(jsonPath("$.data.writer.id").value("1")) .andExpect(jsonPath("$.data.writer.nickname").value("test")) .andExpect(jsonPath("$.data.writer.profileImg").value("https://drive.google.com/file/d/1R9EIOoEWWgPUhY6e-t4VFuqMgknl7rm8/view?usp=sharing")) - .andExpect(jsonPath("$.data.name").value("nintendo")) - .andExpect(jsonPath("$.data.category").value("GAMES")) + .andExpect(jsonPath("$.data.name").value("nintendo")).andExpect(jsonPath("$.data.category").value("GAMES")) .andExpect(jsonPath("$.data.images.size()").value(2)) .andExpect(jsonPath("$.data.images[0]").value("https://picsum.photos/200/300")) .andExpect(jsonPath("$.data.description").value("닌텐도 새 제품")) @@ -64,9 +61,39 @@ class ProductIntegrationTest( .andExpect(jsonPath("$.data.endDate").value("2024-01-04T00:00:00")) .andExpect(jsonPath("$.data.isMine").value("true")) } - + @Test fun postProductIntegrationTest() { + // 이미지 파일 로드 + val imagePath = Paths.get("src/test/resources/img.png") + val imageFile = MockMultipartFile( + "images", "img.png", MediaType.IMAGE_PNG_VALUE, Files.readAllBytes(imagePath) + ) + // JSON 데이터 생성 + val productReqDto = """ + { + "title": "솔 타이틀", + "description": "설명", + "startPrice": 1000, + "startDate": "2024-10-19T15:00:00", + "endDate": "2024-10-23T15:00:00", + "category": "BOOKS" + } + """.trimIndent() + val productReqDtoFile = MockMultipartFile( + "productReqDto", "productReqDto", MediaType.APPLICATION_JSON_VALUE, productReqDto.toByteArray() + ) + + // MockMvc 요청 작성 및 실행 + mockMvc.perform( + MockMvcRequestBuilders.multipart("/products") + .file(imageFile) + .file(productReqDtoFile) + .param("userId", "1") + .contentType(MediaType.MULTIPART_FORM_DATA) + ).andExpect(status().isOk) + .andExpect(jsonPath("$.data.title").value("솔 타이틀")) + .andExpect(jsonPath("$.data.writerId").value(1)) } } \ No newline at end of file diff --git a/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt b/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt index bc7f9e2..5a16787 100644 --- a/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt +++ b/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt @@ -1,6 +1,7 @@ package com.get_offer.product.service import com.get_offer.TestFixtures +import com.get_offer.multipart.ImageService import com.get_offer.product.domain.ProductStatus import com.get_offer.product.repository.ProductRepository import com.get_offer.user.domain.User @@ -21,12 +22,14 @@ class ProductServiceTest { private lateinit var productService: ProductService private lateinit var mockProductRepository: ProductRepository private lateinit var mockUserRepository: UserRepository + private lateinit var mockImageService: ImageService @BeforeEach fun setUp() { mockProductRepository = mock(ProductRepository::class.java) mockUserRepository = mock(UserRepository::class.java) - productService = ProductService(mockProductRepository, mockUserRepository) + mockImageService = mock(ImageService::class.java) + productService = ProductService(mockImageService, mockProductRepository, mockUserRepository) } @Test @@ -41,8 +44,7 @@ class ProductServiceTest { `when`( mockProductRepository.findAllByStatusInOrderByEndDateDesc( - listOf(ProductStatus.IN_PROGRESS, ProductStatus.WAIT), - pageable + listOf(ProductStatus.IN_PROGRESS, ProductStatus.WAIT), pageable ) ).thenReturn( PageImpl(items, pageable, items.size.toLong()) diff --git a/src/test/resources/img.png b/src/test/resources/img.png new file mode 100644 index 0000000000000000000000000000000000000000..0a1f2585007331216f12c06d26ec2e31109a4127 GIT binary patch literal 79172 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 f|9<;ly9U=3+EttTf5`{cA>c<4jKMm6`Dzx literal 0 HcmV?d00001 diff --git a/src/test/resources/test_data.sql b/src/test/resources/test_data.sql index dfb52f9..86f9671 100644 --- a/src/test/resources/test_data.sql +++ b/src/test/resources/test_data.sql @@ -26,6 +26,7 @@ INSERT INTO PRODUCTS (ID, WRITER_ID, TITLE, DESCRIPTION, CATEGORY, CURRENT_PRICE VALUES (3, 1, 'ikea chair', '저쩌', 'FURNITURE', 43000, 8000, 'COMPLETED', '2024-01-06 00:00:00', '2024-01-04 00:00:00', '2024-01-02 00:00:00', '2024-01-02 00:00:00', '{"images":["https://picsum.photos/200/300","https://picsum.photos/200/300"]}'); +ALTER TABLE PRODUCTS ALTER COLUMN ID RESTART WITH 4; INSERT INTO AUCTION_RESULTS (ID, PRODUCT_ID, BUYER_ID, FINAL_PRICE, AUCTION_STATUS, CREATED_AT, UPDATED_AT) From 350e80daca2d152c4c23706f183a571f2ee1e9e3 Mon Sep 17 00:00:00 2001 From: pine_lee Date: Sat, 19 Oct 2024 20:55:24 +0900 Subject: [PATCH 6/6] =?UTF-8?q?feat=20:=20=EC=83=81=ED=92=88=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=9C=A0=EB=8B=9B=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20#7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductServiceTest.kt | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt b/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt index 5a16787..109e76c 100644 --- a/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt +++ b/src/test/kotlin/com/get_offer/product/service/ProductServiceTest.kt @@ -2,21 +2,29 @@ package com.get_offer.product.service import com.get_offer.TestFixtures import com.get_offer.multipart.ImageService +import com.get_offer.product.controller.ProductPostReqDto +import com.get_offer.product.domain.Category +import com.get_offer.product.domain.Product +import com.get_offer.product.domain.ProductImagesVo import com.get_offer.product.domain.ProductStatus import com.get_offer.product.repository.ProductRepository import com.get_offer.user.domain.User import com.get_offer.user.repository.UserRepository +import java.time.LocalDateTime import java.util.* +import org.apache.coyote.BadRequestException import org.assertj.core.api.Assertions import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test +import org.mockito.ArgumentMatchers.anyList import org.mockito.Mockito.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageRequest +import org.springframework.mock.web.MockMultipartFile class ProductServiceTest { private lateinit var productService: ProductService @@ -82,4 +90,92 @@ class ProductServiceTest { Assertions.assertThat(result.images[0]).isEqualTo("https://image1.png") Assertions.assertThat(result.images[1]).isEqualTo("https://image2.png") } + + @Test + fun postProductWithValidData() { + // given + val userId = 1L + val productReqDto = ProductPostReqDto( + title = "Test Product", + description = "Test Description", + startPrice = 1000, + startDate = LocalDateTime.now().plusDays(1), + endDate = LocalDateTime.now().plusDays(3), + category = Category.BOOKS + ) + + val mockImage = MockMultipartFile("images", "test.jpg", "image/jpeg", byteArrayOf(1, 2, 3)) + + val imageUrls = listOf("http://image-url.com/test.jpg") + `when`(mockImageService.saveImages(anyList())).thenReturn(imageUrls) + + val product = Product( + title = productReqDto.title, + category = productReqDto.category, + writerId = userId, + images = ProductImagesVo(imageUrls), + description = productReqDto.description, + startPrice = productReqDto.startPrice, + currentPrice = productReqDto.startPrice, + startDate = productReqDto.startDate, + endDate = productReqDto.endDate, + status = ProductStatus.IN_PROGRESS + ) + + `when`(mockProductRepository.save(any(Product::class.java))).thenReturn(product) + + // when + val result = productService.postProduct(productReqDto, userId, listOf(mockImage)) + + // then + assertNotNull(result) + assertEquals(productReqDto.title, result.title) + } + + @Test + fun postProductWithInvalidStartPrice() { + // given + val userId = 1L + val productReqDto = ProductPostReqDto( + title = "Test Product", + description = "Test Description", + startPrice = -1000, // Invalid start price + startDate = LocalDateTime.now().plusDays(1), + endDate = LocalDateTime.now().plusDays(3), + category = Category.BOOKS + ) + + val mockImage = MockMultipartFile("images", "test.jpg", "image/jpeg", byteArrayOf(1, 2, 3)) + + // when & then + val exception = assertThrows(BadRequestException::class.java) { + productService.postProduct(productReqDto, userId, listOf(mockImage)) + } + + assertEquals("startPrice가 0보다 작을 수 없습니다.", exception.message) + } + + @Test + fun `test postProduct with invalid date range`() { + // given + val userId = 1L + val productReqDto = ProductPostReqDto( + title = "Test Product", + description = "Test Description", + startPrice = 1000, + startDate = LocalDateTime.now().plusDays(10), // Invalid start date (after end date) + endDate = LocalDateTime.now().plusDays(3), + category = Category.BOOKS + ) + + val mockImage = MockMultipartFile("images", "test.jpg", "image/jpeg", byteArrayOf(1, 2, 3)) + + // when & then + val exception = assertThrows(BadRequestException::class.java) { + productService.postProduct(productReqDto, userId, listOf(mockImage)) + } + + assertEquals("시작 날짜가 유효하지 않습니다.", exception.message) + } + } \ No newline at end of file