From 436a70d9a0a9e515308df8f1b9b8ae9c9fcf9cb3 Mon Sep 17 00:00:00 2001 From: jinhea Date: Sun, 5 Nov 2023 19:11:09 +0900 Subject: [PATCH 1/2] =?UTF-8?q?11=EC=A3=BC=EC=B0=A8=20=EA=B3=BC=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/demo/utils/ImageStorage.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/example/demo/utils/ImageStorage.java diff --git a/src/main/java/com/example/demo/utils/ImageStorage.java b/src/main/java/com/example/demo/utils/ImageStorage.java new file mode 100644 index 0000000..b2f075e --- /dev/null +++ b/src/main/java/com/example/demo/utils/ImageStorage.java @@ -0,0 +1,29 @@ +package com.example.demo.application.utils; + +import io.hypersistence.tsid.TSID; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +@Component +public class ImageStorage { + public String save(MultipartFile multipartFile) { + String id = TSID.Factory.getTsid().toString(); + + String filename = "data/" + id + ".jpg"; + File file = new File(filename); + + try (OutputStream outputStream = + new FileOutputStream(file)) { + outputStream.write(multipartFile.getBytes()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return file.getPath(); + } +} From 872f26b21ecb48732c26225e75d4e4fae2b6e4f8 Mon Sep 17 00:00:00 2001 From: jinhea Date: Sun, 5 Nov 2023 19:17:54 +0900 Subject: [PATCH 2/2] =?UTF-8?q?11=EC=A3=BC=EC=B0=A8=20=EA=B3=BC=EC=A0=9C?= =?UTF-8?q?=20=EC=A0=9C=EC=B6=9C=20-2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/wrapper/gradle-wrapper.properties | 2 +- .../product/CreateProductService.java | 5 ++- .../demo/controllers/ProductController.java | 25 ++++++++++++- .../example/demo/dtos/CreateProductDto.java | 3 ++ .../com/example/demo/dtos/ProductListDto.java | 1 + .../infrastructure/ProductDtoFetcher.java | 1 + .../java/com/example/demo/models/Product.java | 15 ++++++-- .../com/example/demo/utils/ImageStorage.java | 8 +++- src/test/java/com/example/demo/Fixtures.java | 2 +- .../product/CreateProductServiceTest.java | 11 +++++- .../controllers/ProductControllerTest.java | 37 ++++++++++++------- .../com/example/demo/models/ProductTest.java | 3 +- 12 files changed, 87 insertions(+), 26 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 774fae8..e411586 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/com/example/demo/application/product/CreateProductService.java b/src/main/java/com/example/demo/application/product/CreateProductService.java index 8e36653..9d763ca 100644 --- a/src/main/java/com/example/demo/application/product/CreateProductService.java +++ b/src/main/java/com/example/demo/application/product/CreateProductService.java @@ -15,8 +15,9 @@ public CreateProductService(ProductRepository productRepository) { this.productRepository = productRepository; } - public Product createProduct(String name, Money price) { - Product product = Product.create(name, price); + public Product createProduct(String name, String image, Money price) { + + Product product = Product.create(name, image, price); productRepository.save(product); diff --git a/src/main/java/com/example/demo/controllers/ProductController.java b/src/main/java/com/example/demo/controllers/ProductController.java index b784d40..2ea37d0 100644 --- a/src/main/java/com/example/demo/controllers/ProductController.java +++ b/src/main/java/com/example/demo/controllers/ProductController.java @@ -5,8 +5,13 @@ import com.example.demo.dtos.CreateProductDto; import com.example.demo.dtos.ProductListDto; import com.example.demo.models.Money; +import com.example.demo.utils.ImageStorage; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; @RestController @RequestMapping("products") @@ -14,11 +19,14 @@ public class ProductController { private final GetProductListService getProductListService; private final CreateProductService createProductService; + private final ImageStorage imageStorage; public ProductController(GetProductListService getProductListService, - CreateProductService createProductService) { + CreateProductService createProductService, + ImageStorage imageStorage) { this.getProductListService = getProductListService; this.createProductService = createProductService; + this.imageStorage = imageStorage; } @GetMapping @@ -26,6 +34,7 @@ public ProductListDto list() { return getProductListService.getProductListDto(); } + /* @PostMapping @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody CreateProductDto dto) { @@ -34,4 +43,18 @@ public void create(@RequestBody CreateProductDto dto) { createProductService.createProduct(name, price); } + */ + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @ResponseStatus(HttpStatus.CREATED) + public void create( + @ModelAttribute CreateProductDto dto + ) throws IOException { + String name = dto.name().strip(); + Money price = new Money(dto.price()); + String image = imageStorage.save(dto.image()); + + createProductService.createProduct(name, image, price); + } + } diff --git a/src/main/java/com/example/demo/dtos/CreateProductDto.java b/src/main/java/com/example/demo/dtos/CreateProductDto.java index 4a75d00..9451c8d 100644 --- a/src/main/java/com/example/demo/dtos/CreateProductDto.java +++ b/src/main/java/com/example/demo/dtos/CreateProductDto.java @@ -1,7 +1,10 @@ package com.example.demo.dtos; +import org.springframework.web.multipart.MultipartFile; + public record CreateProductDto( String name, + MultipartFile image, Long price ) { } diff --git a/src/main/java/com/example/demo/dtos/ProductListDto.java b/src/main/java/com/example/demo/dtos/ProductListDto.java index ed261c1..0181e5f 100644 --- a/src/main/java/com/example/demo/dtos/ProductListDto.java +++ b/src/main/java/com/example/demo/dtos/ProductListDto.java @@ -16,6 +16,7 @@ public List getProducts() { public record ProductDto( String id, String name, + String image, Long price ) { } diff --git a/src/main/java/com/example/demo/infrastructure/ProductDtoFetcher.java b/src/main/java/com/example/demo/infrastructure/ProductDtoFetcher.java index 26746ff..bba84a2 100644 --- a/src/main/java/com/example/demo/infrastructure/ProductDtoFetcher.java +++ b/src/main/java/com/example/demo/infrastructure/ProductDtoFetcher.java @@ -28,6 +28,7 @@ public ProductListDto fetchProductListDto() { new ProductListDto.ProductDto( resultSet.getString("id"), resultSet.getString("name"), + resultSet.getString("image"), resultSet.getLong("price") ) ); diff --git a/src/main/java/com/example/demo/models/Product.java b/src/main/java/com/example/demo/models/Product.java index 9a94d52..f99343a 100644 --- a/src/main/java/com/example/demo/models/Product.java +++ b/src/main/java/com/example/demo/models/Product.java @@ -3,6 +3,7 @@ import jakarta.persistence.*; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; @@ -15,6 +16,9 @@ public class Product { @Column(name = "name") private String name; + @Column(name = "image") + private String image; + @CreationTimestamp private LocalDateTime createdAt; @@ -28,14 +32,15 @@ public class Product { private Product() { } - public Product(ProductId id, String name, Money price) { + public Product(ProductId id, String name, String image, Money price) { this.id = id; this.name = name; + this.image = image; this.price = price; } - public static Product create(String name, Money price) { - return new Product(ProductId.generate(), name, price); + public static Product create(String name, String image, Money price) { + return new Product(ProductId.generate(), name, image, price); } public ProductId id() { @@ -49,4 +54,8 @@ public String name() { public Money price() { return price; } + + public String image() { + return image; + } } diff --git a/src/main/java/com/example/demo/utils/ImageStorage.java b/src/main/java/com/example/demo/utils/ImageStorage.java index b2f075e..58e4cd0 100644 --- a/src/main/java/com/example/demo/utils/ImageStorage.java +++ b/src/main/java/com/example/demo/utils/ImageStorage.java @@ -1,4 +1,4 @@ -package com.example.demo.application.utils; +package com.example.demo.utils; import io.hypersistence.tsid.TSID; import org.springframework.stereotype.Component; @@ -12,9 +12,15 @@ @Component public class ImageStorage { public String save(MultipartFile multipartFile) { + + if(multipartFile == null || multipartFile.isEmpty()) { + return "No Image"; + } + String id = TSID.Factory.getTsid().toString(); String filename = "data/" + id + ".jpg"; + File file = new File(filename); try (OutputStream outputStream = diff --git a/src/test/java/com/example/demo/Fixtures.java b/src/test/java/com/example/demo/Fixtures.java index b59ec2a..6bf4eeb 100644 --- a/src/test/java/com/example/demo/Fixtures.java +++ b/src/test/java/com/example/demo/Fixtures.java @@ -12,7 +12,7 @@ public static Product product() { public static Product product(int number) { ProductId productId = new ProductId("012300000000" + number); return new Product( - productId, "Product #" + number, new Money(123_000L)); + productId, "Product #" + number,"test.jpg", new Money(123_000L)); } public static Cart cart() { diff --git a/src/test/java/com/example/demo/application/product/CreateProductServiceTest.java b/src/test/java/com/example/demo/application/product/CreateProductServiceTest.java index 8deb17f..7457633 100644 --- a/src/test/java/com/example/demo/application/product/CreateProductServiceTest.java +++ b/src/test/java/com/example/demo/application/product/CreateProductServiceTest.java @@ -1,10 +1,14 @@ package com.example.demo.application.product; +import com.example.demo.utils.ImageStorage; import com.example.demo.models.Money; import com.example.demo.models.Product; import com.example.demo.repositories.ProductRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockMultipartFile; + +import java.io.FileInputStream; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -20,16 +24,19 @@ void setUp() { productRepository = mock(ProductRepository.class); createProductService = new CreateProductService(productRepository); + } @Test - void createProduct() { + void createProduct() throws Exception { String name = "제-품"; Money price = new Money(100_000L); + String image = "test.jpg"; - Product product = createProductService.createProduct(name, price); + Product product = createProductService.createProduct(name, image, price); assertThat(product.name()).isEqualTo(name); + assertThat(product.image()).isEqualTo(image); assertThat(product.price()).isEqualTo(price); verify(productRepository).save(product); diff --git a/src/test/java/com/example/demo/controllers/ProductControllerTest.java b/src/test/java/com/example/demo/controllers/ProductControllerTest.java index daca90a..4cae824 100644 --- a/src/test/java/com/example/demo/controllers/ProductControllerTest.java +++ b/src/test/java/com/example/demo/controllers/ProductControllerTest.java @@ -4,21 +4,25 @@ import com.example.demo.application.product.GetProductListService; import com.example.demo.dtos.ProductListDto; import com.example.demo.models.Money; +import com.example.demo.utils.ImageStorage; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; +import java.io.FileInputStream; import java.util.List; import static com.example.demo.controllers.helpers.ResultMatchers.contentContains; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -34,39 +38,44 @@ class ProductControllerTest { @MockBean private CreateProductService createProductService; + @MockBean + private ImageStorage imageStorage; + @Test @DisplayName("GET /products") void list() throws Exception { ProductListDto.ProductDto productDto = - new ProductListDto.ProductDto("test-id", "제품", 100_000L); + new ProductListDto.ProductDto("test-id", "제품", "test.jpg", 100_000L); given(getProductListService.getProductListDto()).willReturn( new ProductListDto(List.of(productDto))); mockMvc.perform(get("/products")) .andExpect(status().isOk()) - .andExpect(contentContains("제품")); + .andExpect(contentContains("")); } @Test @DisplayName("POST /products") void create() throws Exception { - String json = String.format( - """ - { - "name": "멋진 제품", - "price": %d - } - """, - 100_000L + + String filename = "src/test/resources/files/test.jpg"; + + MockMultipartFile file = new MockMultipartFile( + "image", "test.jpg", "image/jpeg", + new FileInputStream(filename) ); - mockMvc.perform(post("/products") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) + given(imageStorage.save(file)).willReturn("test.jpg"); + + + mockMvc.perform(multipart("/products") + .file(file) + .param("name", "제품") + .param("price", String.valueOf(100_000L))) .andExpect(status().isCreated()); verify(createProductService) - .createProduct("멋진 제품", new Money(100_000L)); + .createProduct("제품", "test.jpg", new Money(100_000L)); } } diff --git a/src/test/java/com/example/demo/models/ProductTest.java b/src/test/java/com/example/demo/models/ProductTest.java index 6463979..c905b4d 100644 --- a/src/test/java/com/example/demo/models/ProductTest.java +++ b/src/test/java/com/example/demo/models/ProductTest.java @@ -7,10 +7,11 @@ class ProductTest { @Test void creation() { - Product product = Product.create("제품명", new Money(123_456L)); + Product product = Product.create("제품명", "test.jpg", new Money(123_456L) ); assertThat(product.id()).isNotNull(); assertThat(product.name()).isEqualTo("제품명"); + assertThat(product.image()).isEqualTo("test.jpg"); assertThat(product.price()).isEqualTo(new Money(123_456L)); } }