Repository: team-tandanji/taco-core-server
Pull request #11: Feature/product server
Author: login: Ymkkg
Labels: name: enhancement
description: New feature or request
Created: 2025-03-12T13:06:05Z | Merged: 2025-03-16T11:52:22Z

Motivation:

Make get delete update api for client

Modifications:

- implement delete by id api
- implement update by id api
- implement read all products, read product by id and products by keyword api
- product validation check from client
- global exception handler for doesn't exist id

Result:

- Closes #6 
- can use read, update, delete api


Linked issue #6: Implement APIs related to Products

Diff:
diff --git a/taco-core-server/pom.xml b/taco-core-server/pom.xml
index 8d32b80..76d7433 100644
--- a/taco-core-server/pom.xml
+++ b/taco-core-server/pom.xml
@@ -57,9 +57,9 @@
 			<groupId>org.projectlombok</groupId>
 			<artifactId>lombok</artifactId>
 			<scope>provided</scope>
-    </dependency>
-  
-    <dependency>
+    	</dependency>
+
+		<dependency>
 			<groupId>com.h2database</groupId>
 			<artifactId>h2</artifactId>
 			<scope>runtime</scope>
@@ -71,6 +71,11 @@
 			<version>3.3.5</version>
 		</dependency>
 
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-validation</artifactId>
+		</dependency>
+
 	</dependencies>
 
 	<build>
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
index ab41300..2a89562 100644
--- a/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
@@ -2,26 +2,89 @@
 
 import com.tandanji.taco_core_server.domain.Product;
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    @Transactional
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public Product saveImage(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = Paths.get(System.getProperty("user.dir"),"image").toString();
 
-        if(result == 1) {
-            return product;
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
+
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        return productRepository.getProducts();
+    }
+
+    public Product getProductById(Long id) {
+        return productRepository.getProductById(id);
+    }
+
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        validationService.checkValidBlank(keyword);
+
+        return productRepository.getProductsByKeyword(keyword.trim());
+    }
+
+    @Transactional
+    public int deleteProductById(Long id) {
+        return productRepository.deleteProductById(id);
+    }
+
+    @Transactional
+    public void updateProduct(Long id, MultipartFile image, Product updateProduct) throws IOException {
+        validationService.checkValid(updateProduct);
+
+        updateProduct.setId(id);
+        updateProduct.setImagePath(productRepository.getProductById(id).getImagePath());
+
+        updateProduct = saveImage(updateProduct, image);
+
+        productRepository.updateProduct(updateProduct);
     }
 }
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ValidationService.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ValidationService.java
new file mode 100644
index 0000000..b232d7a
--- /dev/null
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ValidationService.java
@@ -0,0 +1,20 @@
+package com.tandanji.taco_core_server.application;
+
+import jakarta.validation.Valid;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+@Service
+@Validated
+public class ValidationService {
+    public <T> void checkValid(@NotNull @Valid T validationTarget) {
+        //do nothing
+    }
+
+    public <T extends CharSequence> void checkValidBlank(@NotBlank T validationTarget) {
+
+    }
+}
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/domain/Product.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/domain/Product.java
index 5a3db8c..ef08ffa 100644
--- a/taco-core-server/src/main/java/com/tandanji/taco_core_server/domain/Product.java
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/domain/Product.java
@@ -1,77 +1,32 @@
 package com.tandanji.taco_core_server.domain;
 
+import jakarta.validation.constraints.*;
+import lombok.Getter;
+import lombok.Setter;
+
+
+@Getter
+@Setter
 public class Product {
     private Long id;
+
+    @NotNull
+    @Size(min = 1, message = "Title Must be at least one character long")
     private String title;
+
     private String imagePath;
     private String tradeMethod;
-    private Long price;
-    private boolean priceOffer;
-    private String description;
-    private String location;
-
-
-    public Long getId() {
-        return id;
-    }
-
-    public String getTitle() {
-        return title;
-    }
-
-    public String getImagePath() {
-        return imagePath;
-    }
-
-    public String getTradeMethod() {
-        return tradeMethod;
-    }
 
-    public Long getPrice() {
-        return price;
-    }
+    @NotNull
+    private String category;
 
-    public boolean isPriceOffer() {
-        return priceOffer;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public String getLocation() {
-        return location;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public void setTitle(String title) {
-        this.title = title;
-    }
-
-    public void setImagePath(String imagePath) {
-        this.imagePath = imagePath;
-    }
-
-    public void setTradeMethod(String tradeMethod) {
-        this.tradeMethod = tradeMethod;
-    }
-
-    public void setPrice(Long price) {
-        this.price = price;
-    }
-
-    public void setPriceOffer(boolean priceOffer) {
-        this.priceOffer = priceOffer;
-    }
+    @PositiveOrZero(message = "Price can not be negative")
+    private Long price;
 
-    public void setDescription(String description) {
-        this.description = description;
-    }
+    private boolean priceOffer;
 
-    public void setLocation(String location) {
-        this.location = location;
-    }
+    @NotNull
+    @Size(min = 5, message = "Description Must be at least five character long")
+    private String description;
+    private String location;
 }
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
index a383c60..ee51df7 100644
--- a/taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
@@ -1,27 +1,80 @@
 package com.tandanji.taco_core_server.infrastructure;
 
 import com.tandanji.taco_core_server.domain.Product;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 @Repository
 public class ProductRepository {
 
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final JdbcTemplate jdbcTemplate;
 
     @Autowired
-    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate) {
         this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+        this.jdbcTemplate = jdbcTemplate;
     }
 
     public int createProduct(Product product) {
-        String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, price, priceOffer, description, location)"
-                + "VALUES (:title, :imagePath, :tradeMethod, :price, :priceOffer, :description, :location)";
+        final String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, category, price, priceOffer, description, location)"
+                + "VALUES (:title, :imagePath, :tradeMethod, :category, :price, :priceOffer, :description, :location)";
 
         BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(product);
         return namedParameterJdbcTemplate.update(sql, parameterSource);
     }
 
+    public List<Product> getProducts() {
+        final String sql = "SELECT * FROM PRODUCTS";
+
+        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public Product getProductById(Long id) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE id = ?";
+
+        return jdbcTemplate.queryForObject(sql,
+                new Object[]{id},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE TITLE LIKE ?";
+
+        String includeKeyword = "%" + keyword + "%";
+
+        //TODO - If didn't find searched item what message should i send?
+        return jdbcTemplate.query(sql,
+                new Object[]{includeKeyword},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    @Transactional
+    public int deleteProductById(Long id) {
+        final String sql = "DELETE FROM PRODUCTS WHERE id = ?";
+
+        return jdbcTemplate.update(sql,id);
+    }
+
+    public int updateProduct(Product product) {
+        final String sql = "UPDATE PRODUCTS SET title = :title, imagePath = :imagePath, tradeMethod = :tradeMethod, "
+                + "category = :category, price = :price, priceOffer = :priceOffer, description = :description, "
+                + "location = :location "
+                + "WHERE id = :id";
+
+        BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(product);
+        return namedParameterJdbcTemplate.update(sql, parameterSource);
+    }
 }
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ErrorMessage.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ErrorMessage.java
new file mode 100644
index 0000000..fb17238
--- /dev/null
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ErrorMessage.java
@@ -0,0 +1,15 @@
+package com.tandanji.taco_core_server.presentation;
+
+import java.util.List;
+
+public class ErrorMessage {
+    private List<String> errors;
+
+    public ErrorMessage(List<String> errors) {
+        this.errors = errors;
+    }
+
+    public List<String> getErrors() {
+        return errors;
+    }
+}
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/GlobalExceptionHandler.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/GlobalExceptionHandler.java
new file mode 100644
index 0000000..eaed2d3
--- /dev/null
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/GlobalExceptionHandler.java
@@ -0,0 +1,46 @@
+package com.tandanji.taco_core_server.presentation;
+
+import com.tandanji.taco_core_server.domain.Product;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import jakarta.validation.Path;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import java.util.List;
+import java.util.Set;
+
+@RestControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(ConstraintViolationException.class)
+    public ResponseEntity<ErrorMessage> handleContstraintViolatedException(ConstraintViolationException ex) {
+        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
+
+        List<String> errors = constraintViolations.stream()
+                .map(constraintViolation -> extractField(constraintViolation.getPropertyPath()) + " : " +
+                        constraintViolation.getMessage()
+                ).toList();
+
+        ErrorMessage errorMessage = new ErrorMessage(errors);
+        return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
+    }
+
+    private String extractField(Path path) {
+        String[] splittedArray = path.toString().split("[.]");
+        int lastIndex = splittedArray.length - 1;
+        return splittedArray[lastIndex];
+    }
+
+    @ExceptionHandler(EmptyResultDataAccessException.class)
+    public ResponseEntity<String> handleEmptyResultDataAccessException(EmptyResultDataAccessException ex) {
+
+        String errorMessage = "No data found for the given ID";
+
+        return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
+    }
+}
diff --git a/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
index c8d6cd5..e229b2b 100644
--- a/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
+++ b/taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
@@ -3,8 +3,13 @@
 import com.tandanji.taco_core_server.application.ProductsService;
 import com.tandanji.taco_core_server.domain.Product;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.List;
 
 @RestController
 public class ProductController {
@@ -16,25 +21,54 @@ public class ProductController {
         this.productsService = productsService;
     }
 
-    @PostMapping(value = "/product/create")
-    public ResponseEntity<?> createProduct(@RequestBody Product product) {
-        Product product1 = productsService.createProduct(product);
-        return ResponseEntity.ok().body(product1);
+    @PostMapping("/products")
+    public ResponseEntity<Void> createProduct(@RequestPart("product") Product product,
+                                              @RequestPart(value = "image", required = false) MultipartFile image) throws IOException {
+
+        productsService.createProduct(product, image);
+
+        return ResponseEntity.status(HttpStatus.CREATED).build();
     }
 
-    @GetMapping("/product/read")
-    public ResponseEntity<?> getProducts() {
-        return ResponseEntity.ok().body(null);
+
+    @GetMapping("/products")
+    public ResponseEntity<List<Product>> getAllProducts() {
+        List<Product> products = productsService.getProducts();
+
+        return ResponseEntity.ok().body(products);
     }
 
-    @PutMapping("/product/update/{id}")
-    public ResponseEntity<?> updateProduct() {
-        return ResponseEntity.ok().body(null);
+    @GetMapping("/products/search/{id}")
+    public ResponseEntity<Product> getProductById(@PathVariable Long id) {
+        Product product = productsService.getProductById(id);
+
+        return ResponseEntity.ok().body(product);
     }
 
-    @DeleteMapping("/product/delete/{id}")
-    public ResponseEntity<?> deleteProduct() {
-        return ResponseEntity.ok().body(null);
+    @GetMapping("/products/search/")
+    public ResponseEntity<List<Product>> getProductsByKeyword(@RequestParam("keyword") String keyword) {
+        List<Product> products = productsService.getProductsByKeyword(keyword);
+
+        return ResponseEntity.ok().body(products);
+    }
+
+    @PutMapping("/products/{id}")
+    public ResponseEntity<String> updateProduct(@PathVariable Long id, @RequestPart(value = "image", required = false) MultipartFile image,
+                                                @RequestPart("product") Product product) throws IOException{
+        productsService.updateProduct(id,image,product);
+
+        return ResponseEntity.status(HttpStatus.OK).body("Successfully Updated");
+    }
+
+    @DeleteMapping("/products/{id}")
+    public ResponseEntity<String> deleteProduct(@PathVariable Long id) {
+        int isDeleted = productsService.deleteProductById(id);
+
+        if(isDeleted == 1) {
+            return ResponseEntity.status(HttpStatus.OK).body("ID: " + id + " is deleted");
+        } else {
+            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Product with ID : " + id + " does not exist");
+        }
     }
 
 }
diff --git a/taco-core-server/src/main/resources/schema.sql b/taco-core-server/src/main/resources/schema.sql
index 718ef40..145a1e4 100644
--- a/taco-core-server/src/main/resources/schema.sql
+++ b/taco-core-server/src/main/resources/schema.sql
@@ -4,6 +4,7 @@ CREATE TABLE products
      title VARCHAR(255),
      imagePath VARCHAR(255),
      tradeMethod VARCHAR(255),
+     category VARCHAR(255),
      price BIGINT,
      priceOffer BOOLEAN,
      description TEXT,


Reviews:
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:18:49Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:20:53Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:22:33Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:22:42Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:23:15Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:24:31Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:25:42Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:26:01Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:26:08Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:27:03Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:27:23Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:33:40Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:37:26Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:38:37Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:39:53Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T13:41:12Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T12:24:25Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T12:30:16Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T12:48:46Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:43:49Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T13:45:09Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:03:02Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:03:55Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:04:02Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:04:40Z
user: login: Ymkkg
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-13T14:19:31Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:23:26Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:24:40Z
user: login: icepeppermint
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:26:22Z
user: login: icepeppermint
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T14:19:40Z

Review comments:
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: I'm not sure, but I think the code below should work fine as well instead of assembling directory path manually
```suggestion
            String imageSaveDir = Paths.get(System.getProperty("user.dir"), "image").toString();
```
created_at: 2025-03-12T13:18:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Nit: it would be good to consider about validating nullability for `product`
created_at: 2025-03-12T13:20:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Nit: you can just return like `return productRepository.getProducts();` not to assign to local variable
created_at: 2025-03-12T13:22:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Ditto
created_at: 2025-03-12T13:22:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Is this works well when the `id` is null value??
created_at: 2025-03-12T13:23:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Actually, you don't need to attach annotation `@Transactional` on this method, but it's good to have a habit to attach the annotation explicitly
created_at: 2025-03-12T13:24:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: If you don't attach the `@Transactional` here, the Spring framework will use a default transaction implicitly, but it's better to use the annotation explicitly.
created_at: 2025-03-12T13:25:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Ditto
created_at: 2025-03-12T13:26:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
+
+        return products;
+    }
+
+    public int deleteProductById(Long id) {
+        int isDeleted = productRepository.deleteProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Ditto
created_at: 2025-03-12T13:26:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
+
+        return products;
+    }
+
+    public int deleteProductById(Long id) {
+        int isDeleted = productRepository.deleteProductById(id);
+
+        return isDeleted;
+    }
+
+    public void updateProduct(Long id, MultipartFile image, Product updateProduct) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Ditto: checking argument's validity
created_at: 2025-03-12T13:27:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+package com.tandanji.taco_core_server.application;
+
+import jakarta.validation.Valid;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+@Service
+@Validated
+public class ValidationService {
+    public <T> void checkValid(@Valid T validationTarget) {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ValidationService.java
user: login: icepeppermint
body: Good idea 👍 
created_at: 2025-03-12T13:27:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: I saw the code below from controller class, the `image` is marked as `required = false`, so you must consider about the `null` case
`@RequestPart(value = "image", required = false) MultipartFile image`
created_at: 2025-03-12T13:33:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,25 +21,54 @@ public class ProductController {
         this.productsService = productsService;
     }
 
-    @PostMapping(value = "/product/create")
-    public ResponseEntity<?> createProduct(@RequestBody Product product) {
-        Product product1 = productsService.createProduct(product);
-        return ResponseEntity.ok().body(product1);
+    @PostMapping("/products")
+    public ResponseEntity<Void> createProduct(@RequestPart("product") Product product,
+                                              @RequestPart(value = "image", required = false) MultipartFile image) throws IOException {
+
+        productsService.createProduct(product, image);
+
+        return ResponseEntity.status(HttpStatus.CREATED).body(null);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
user: login: icepeppermint
body: Nit: how about not to call `.body(null)`, just using `ResponseEntity.status(HttpStatus.CREATED)`??
created_at: 2025-03-12T13:37:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
+
+        return products;
+    }
+
+    public int deleteProductById(Long id) {
+        int isDeleted = productRepository.deleteProductById(id);
+
+        return isDeleted;
+    }
+
+    public void updateProduct(Long id, MultipartFile image, Product updateProduct) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Ditto: attach annotation `@Transactional` explicitly
created_at: 2025-03-12T13:38:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,27 +1,77 @@
 package com.tandanji.taco_core_server.infrastructure;
 
 import com.tandanji.taco_core_server.domain.Product;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.stereotype.Repository;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 @Repository
 public class ProductRepository {
 
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final JdbcTemplate jdbcTemplate;
 
     @Autowired
-    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate) {
         this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+        this.jdbcTemplate = jdbcTemplate;
     }
 
     public int createProduct(Product product) {
-        String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, price, priceOffer, description, location)"
-                + "VALUES (:title, :imagePath, :tradeMethod, :price, :priceOffer, :description, :location)";
+        final String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, category, price, priceOffer, description, location)"
+                + "VALUES (:title, :imagePath, :tradeMethod, :category, :price, :priceOffer, :description, :location)";
 
         BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(product);
         return namedParameterJdbcTemplate.update(sql, parameterSource);
     }
 
+    public List<Product> getProducts() {
+        final String sql = "SELECT * FROM PRODUCTS";
+
+        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public Product getProductById(Long id) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE id = ?";
+
+        return jdbcTemplate.queryForObject(sql,
+                new Object[]{id},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
user: login: icepeppermint
body: How does the method work if the `keyword` is null or blank?
created_at: 2025-03-12T13:39:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,27 +1,77 @@
 package com.tandanji.taco_core_server.infrastructure;
 
 import com.tandanji.taco_core_server.domain.Product;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.stereotype.Repository;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 @Repository
 public class ProductRepository {
 
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final JdbcTemplate jdbcTemplate;
 
     @Autowired
-    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate) {
         this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+        this.jdbcTemplate = jdbcTemplate;
     }
 
     public int createProduct(Product product) {
-        String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, price, priceOffer, description, location)"
-                + "VALUES (:title, :imagePath, :tradeMethod, :price, :priceOffer, :description, :location)";
+        final String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, category, price, priceOffer, description, location)"
+                + "VALUES (:title, :imagePath, :tradeMethod, :category, :price, :priceOffer, :description, :location)";
 
         BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(product);
         return namedParameterJdbcTemplate.update(sql, parameterSource);
     }
 
+    public List<Product> getProducts() {
+        final String sql = "SELECT * FROM PRODUCTS";
+
+        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public Product getProductById(Long id) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE id = ?";
+
+        return jdbcTemplate.queryForObject(sql,
+                new Object[]{id},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE TITLE LIKE ?";
+
+        String includeKeyword = "%" + keyword + "%";
+
+        //TODO - If didn't find searched item what message should i send?
+        return jdbcTemplate.query(sql,
+                new Object[]{includeKeyword},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public int deleteProductById(Long id) {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
user: login: icepeppermint
body: Ditto: `@Transactional`
created_at: 2025-03-12T13:41:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,25 +21,54 @@ public class ProductController {
         this.productsService = productsService;
     }
 
-    @PostMapping(value = "/product/create")
-    public ResponseEntity<?> createProduct(@RequestBody Product product) {
-        Product product1 = productsService.createProduct(product);
-        return ResponseEntity.ok().body(product1);
+    @PostMapping("/products")
+    public ResponseEntity<Void> createProduct(@RequestPart("product") Product product,
+                                              @RequestPart(value = "image", required = false) MultipartFile image) throws IOException {
+
+        productsService.createProduct(product, image);
+
+        return ResponseEntity.status(HttpStatus.CREATED).body(null);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
user: login: Ymkkg
body: If remove .body() method it doesn't work
if i want to send just status code there are two options
1. change return type to void and add annotation @responsestatus(httpstatus.created)
2. change body(null) to .build()
I'm not sure which is the better
created_at: 2025-03-13T12:24:25Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: It works well👍 I applied it
created_at: 2025-03-13T12:30:16Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: I added nullability check for product
created_at: 2025-03-13T12:48:46Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: Ok I applied it
created_at: 2025-03-13T13:43:48Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: It works well if id is null value 
because jdbctemplate throws EmptyResultDataAccessException so it handled in globalexception
created_at: 2025-03-13T13:45:09Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: I don't know if i misunderstood, if image is null just only save imagePath in else paragraph in saveImage Function it doesn't enough?
created_at: 2025-03-13T14:03:01Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: Ok I applied it
created_at: 2025-03-13T14:03:55Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
+
+        return products;
+    }
+
+    public int deleteProductById(Long id) {
+        int isDeleted = productRepository.deleteProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: Ok I applied it
created_at: 2025-03-13T14:04:02Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
+
+        return product;
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
+        List<Product> products = productRepository.getProductsByKeyword(keyword);
+
+        return products;
+    }
+
+    public int deleteProductById(Long id) {
+        int isDeleted = productRepository.deleteProductById(id);
+
+        return isDeleted;
+    }
+
+    public void updateProduct(Long id, MultipartFile image, Product updateProduct) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: Ymkkg
body: Ok I applied it😊
created_at: 2025-03-13T14:04:40Z
author_association: COLLABORATOR
diff_hunk: @@ -1,27 +1,77 @@
 package com.tandanji.taco_core_server.infrastructure;
 
 import com.tandanji.taco_core_server.domain.Product;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero;
+import jakarta.validation.constraints.Size;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
 import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
 import org.springframework.stereotype.Repository;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
 
 @Repository
 public class ProductRepository {
 
     private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
+    private final JdbcTemplate jdbcTemplate;
 
     @Autowired
-    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
+    ProductRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate, JdbcTemplate jdbcTemplate) {
         this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
+        this.jdbcTemplate = jdbcTemplate;
     }
 
     public int createProduct(Product product) {
-        String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, price, priceOffer, description, location)"
-                + "VALUES (:title, :imagePath, :tradeMethod, :price, :priceOffer, :description, :location)";
+        final String sql = "INSERT INTO PRODUCTS (title, imagePath, tradeMethod, category, price, priceOffer, description, location)"
+                + "VALUES (:title, :imagePath, :tradeMethod, :category, :price, :priceOffer, :description, :location)";
 
         BeanPropertySqlParameterSource parameterSource = new BeanPropertySqlParameterSource(product);
         return namedParameterJdbcTemplate.update(sql, parameterSource);
     }
 
+    public List<Product> getProducts() {
+        final String sql = "SELECT * FROM PRODUCTS";
+
+        return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public Product getProductById(Long id) {
+        final String sql = "SELECT * FROM PRODUCTS WHERE id = ?";
+
+        return jdbcTemplate.queryForObject(sql,
+                new Object[]{id},
+                new BeanPropertyRowMapper<>(Product.class));
+    }
+
+    public List<Product> getProductsByKeyword(String keyword) {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/infrastructure/ProductRepository.java
user: login: Ymkkg
body: if keyword is null it returns null but in blank case it return all data
so i change code for checking blink
created_at: 2025-03-13T14:19:31Z
author_association: COLLABORATOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: Oh, I see. You're right. I didn't see the else paragraph you mentioned.
created_at: 2025-03-13T14:23:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,24 +4,82 @@
 import com.tandanji.taco_core_server.infrastructure.ProductRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
 
 @Service
 public class ProductsService {
 
-    private ProductRepository productRepository;
+    private final ProductRepository productRepository;
+    private final ValidationService validationService;
 
     @Autowired
-    ProductsService(ProductRepository productRepository) {
+    ProductsService(ProductRepository productRepository, ValidationService validationService) {
         this.productRepository = productRepository;
+        this.validationService = validationService;
+    }
+
+    public void createProduct(Product product, MultipartFile image) throws IOException {
+        validationService.checkValid(product);
+
+        product = saveImage(product, image);
+
+        productRepository.createProduct(product);
     }
 
-    public Product createProduct(Product product) {
-        int result = productRepository.createProduct(product);
+    public static Product saveImage(Product product, MultipartFile image) throws IOException {
+        if (image != null && !image.isEmpty()) {
+            String imageSaveDir = System.getProperty("user.dir") + "/image";
+
+            String imageFileName = System.currentTimeMillis() + "-" + image.getOriginalFilename();
+            Path imagePath = Paths.get(imageSaveDir, imageFileName);
 
-        if(result == 1) {
-            return product;
+            Files.createDirectories(imagePath.getParent());
+            image.transferTo(imagePath.toFile());
+
+            product.setImagePath(imagePath.toString());
         } else {
-            return null;
+            product.setImagePath(product.getImagePath());
         }
+
+        return product;
+    }
+
+    public List<Product> getProducts() {
+        List<Product> products = productRepository.getProducts();
+
+        return products;
+    }
+
+    public Product getProductById(Long id) {
+        Product product = productRepository.getProductById(id);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/application/ProductsService.java
user: login: icepeppermint
body: If that's the operation you expect, it's not an issue.
created_at: 2025-03-13T14:24:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,25 +21,54 @@ public class ProductController {
         this.productsService = productsService;
     }
 
-    @PostMapping(value = "/product/create")
-    public ResponseEntity<?> createProduct(@RequestBody Product product) {
-        Product product1 = productsService.createProduct(product);
-        return ResponseEntity.ok().body(product1);
+    @PostMapping("/products")
+    public ResponseEntity<Void> createProduct(@RequestPart("product") Product product,
+                                              @RequestPart(value = "image", required = false) MultipartFile image) throws IOException {
+
+        productsService.createProduct(product, image);
+
+        return ResponseEntity.status(HttpStatus.CREATED).body(null);
path: taco-core-server/src/main/java/com/tandanji/taco_core_server/presentation/ProductController.java
user: login: icepeppermint
body: I think it would better to use just `build()` method instead of using an annotation `@ResponseStatus(HttpStatus.CREATED)`.
created_at: 2025-03-13T14:26:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: icepeppermint
created_at: 2025-03-12T13:47:20Z
body: I just reviewed this pull request, it looks great. For the next step, you can consider to use logging framework (e.g. log4j2, logback) Since the code written so far has no logs, it's very difficult to trace the root cause when something goes wrong in the application. So, it would be very helpful to have logs related to the root cause. (Of course, if any exceptions are raised, the stacktrace will be remained, but sometimes there will be very helpful if you write logs yourself)
author_association: CONTRIBUTOR
user: login: icepeppermint
created_at: 2025-03-13T03:14:56Z
body: I don't think that test cases need to be added for this pull request, but based on the issue below, I expect you to add test cases in the future. I think you're doing a great job so far, keep it up.
- https://github.com/team-tandanji/taco-core-server/issues/9
author_association: CONTRIBUTOR
user: login: Ymkkg
created_at: 2025-03-13T04:22:11Z
body: Thank you for the review there are many tasks to be done so i will comment every review after think about code change 
author_association: COLLABORATOR
user: login: icepeppermint
created_at: 2025-03-13T14:28:14Z
body: Did you push the changes to origin? I cannot see the changes you mentioned.
author_association: CONTRIBUTOR