diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 282f50ac..ead0ef36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 17 + - name: Set up JDK 11 uses: actions/setup-java@v2 with: - java-version: '17' + java-version: '11' distribution: 'adopt' cache: maven - name: Build with Maven diff --git a/README.md b/README.md index 12cfbd53..c7e6e532 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Your task is: - id - title - price - - category (one product can have one category but the category can have multiple products) + - category (one product can have one category) - create model `Category` with fields - id - name @@ -29,5 +29,3 @@ Your task is: - delete Category by ID - update Category - create required DTOs and mappers - -__Before submitting solution make sure you checked it first with__ [checklist](https://mate-academy.github.io/jv-program-common-mistakes/java-spring-boot/spring-data-jpa/jv-springboot-data-jpa.html) diff --git a/pom.xml b/pom.xml index cf704585..21fd74a5 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 3.0.0 + 2.6.0 mate.academy.springboot @@ -14,7 +14,7 @@ data-jpa Demo project for Spring Boot - 17 + 11 3.1.1 https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml @@ -29,8 +29,63 @@ org.springframework.boot spring-boot-autoconfigure + 2.6.5 + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-starter-data-jpa + + + io.springfox + springfox-swagger-ui + 3.0.0 + + + io.springfox + springfox-swagger2 + 3.0.0 + + + io.springfox + springfox-boot-starter + 3.0.0 + + + + org.springframework.boot + spring-boot-starter-parent + 2.7.5 + pom + + + + + com.h2database + h2 + + + org.projectlombok + lombok + 1.18.24 + provided + + + org.springframework.boot + spring-boot-maven-plugin + 2.7.5 + diff --git a/src/main/java/mate/academy/springboot/datajpa/config/SpringFoxConfig.java b/src/main/java/mate/academy/springboot/datajpa/config/SpringFoxConfig.java new file mode 100644 index 00000000..1501c6b7 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/config/SpringFoxConfig.java @@ -0,0 +1,25 @@ +package mate.academy.springboot.datajpa.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.data.rest.configuration.SpringDataRestConfiguration; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +@EnableSwagger2 +@Configuration +@Import(SpringDataRestConfiguration.class) +public class SpringFoxConfig { + @Bean + public Docket api() { + return new Docket(DocumentationType.SWAGGER_2) + .select() + .apis(RequestHandlerSelectors.basePackage("mate.academy.springboot.datajpa")) + .paths(PathSelectors.any()) + .build(); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/controller/CategoryController.java b/src/main/java/mate/academy/springboot/datajpa/controller/CategoryController.java new file mode 100644 index 00000000..ddc2b7cb --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/controller/CategoryController.java @@ -0,0 +1,50 @@ +package mate.academy.springboot.datajpa.controller; + +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dto.request.CategoryRequestDto; +import mate.academy.springboot.datajpa.dto.response.CategoryResponseDto; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.services.CategoryService; +import mate.academy.springboot.datajpa.services.mapper.CategoryMapper; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/categories") +@RequiredArgsConstructor +public class CategoryController { + private final CategoryService categoryService; + private final CategoryMapper categoryMapper; + + @PostMapping + public CategoryResponseDto create(@RequestBody + CategoryRequestDto categoryRequestDto) { + return categoryMapper.toDto( + categoryService.create( + categoryMapper.toModel(categoryRequestDto))); + } + + @GetMapping("/{id}") + public CategoryResponseDto getById(@PathVariable Long id) { + return categoryMapper.toDto(categoryService.getById(id)); + } + + @DeleteMapping("/{id}") + public void deleteById(@PathVariable Long id) { + categoryService.deleteById(id); + } + + @PutMapping("/{id}") + public CategoryResponseDto update(@PathVariable Long id, + @RequestBody CategoryRequestDto categoryRequestDto) { + Category category = categoryService.getById(id); + category.setName(categoryRequestDto.getName()); + return categoryMapper.toDto(categoryService.create(category)); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/controller/InjectController.java b/src/main/java/mate/academy/springboot/datajpa/controller/InjectController.java new file mode 100644 index 00000000..86ab16fe --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/controller/InjectController.java @@ -0,0 +1,68 @@ +package mate.academy.springboot.datajpa.controller; + +import java.math.BigDecimal; +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.services.CategoryService; +import mate.academy.springboot.datajpa.services.ProductService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class InjectController { + private final ProductService productService; + private final CategoryService categoryService; + + @GetMapping("/inject") + public String inject() { + Category product = new Category(); + product.setName("products"); + product = categoryService.create(product); + + Category toys = new Category(); + toys.setName("toys"); + toys = categoryService.create(toys); + + Category drinks = new Category(); + drinks.setName("drinks"); + drinks = categoryService.create(drinks); + + Category smartPhone = new Category(); + smartPhone.setName("smartPhone"); + smartPhone = categoryService.create(smartPhone); + + Product pizza = new Product(); + pizza.setCategory(product); + pizza.setPrice(BigDecimal.valueOf(1.4)); + pizza.setTitle("pizza"); + pizza = productService.create(pizza); + + Product bear = new Product(); + bear.setCategory(toys); + bear.setPrice(BigDecimal.valueOf(2.1)); + bear.setTitle("bear"); + bear = productService.create(bear); + + Product coffee = new Product(); + coffee.setCategory(drinks); + coffee.setPrice(BigDecimal.valueOf(1.13)); + coffee.setTitle("coffee"); + coffee = productService.create(coffee); + + Product phone = new Product(); + phone.setCategory(smartPhone); + phone.setPrice(BigDecimal.valueOf(124.5)); + phone.setTitle("phone"); + phone = productService.create(phone); + + Product beer = new Product(); + beer.setCategory(drinks); + beer.setPrice(BigDecimal.valueOf(2.1)); + beer.setTitle("beer"); + beer = productService.create(beer); + + return "categories and products was created"; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/controller/ProductController.java b/src/main/java/mate/academy/springboot/datajpa/controller/ProductController.java new file mode 100644 index 00000000..713cc4fc --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/controller/ProductController.java @@ -0,0 +1,80 @@ +package mate.academy.springboot.datajpa.controller; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dto.request.ProductRequestDto; +import mate.academy.springboot.datajpa.dto.response.ProductResponseDto; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.services.CategoryService; +import mate.academy.springboot.datajpa.services.ProductService; +import mate.academy.springboot.datajpa.services.mapper.ProductMapper; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/products") +@RequiredArgsConstructor +public class ProductController { + private final ProductService productService; + private final ProductMapper productMapper; + private final CategoryService categoryService; + + @PostMapping + public ProductResponseDto create(@RequestBody ProductRequestDto productRequestDto) { + return productMapper.toDto( + productService.create( + productMapper.toModel(productRequestDto))); + } + + @GetMapping("/{id}") + public ProductResponseDto getById(@PathVariable Long id) { + return productMapper.toDto(productService.getById(id)); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public void delete(@PathVariable Long id) { + productService.deleteById(id); + } + + @PutMapping("/{id}") + public ProductResponseDto update(@PathVariable Long id, + @RequestBody ProductRequestDto productRequestDto) { + Product product = productMapper.toModel(productRequestDto); + product.setId(id); + return productMapper.toDto(productService.create(product)); + } + + @GetMapping("/by-price") + public List getAllProductsWithPriceBetween(@RequestParam BigDecimal from, + @RequestParam BigDecimal to) { + return productService.findAllByPriceBetween(from, to).stream() + .map(p -> productMapper.toDto(p)) + .collect(Collectors.toList()); + } + + @GetMapping("/by-categories") + public List getAllWithCategory(@RequestParam String category) { + List categoriesId = categoryService.getCategoriesByNameIn( + Arrays.asList(category.split(","))) + .stream() + .map(Category::getId) + .collect(Collectors.toList()); + return productService.getAllWithCategories(categoriesId).stream() + .map(productMapper::toDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dto/request/CategoryRequestDto.java b/src/main/java/mate/academy/springboot/datajpa/dto/request/CategoryRequestDto.java new file mode 100644 index 00000000..f3fa8acd --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/request/CategoryRequestDto.java @@ -0,0 +1,8 @@ +package mate.academy.springboot.datajpa.dto.request; + +import lombok.Data; + +@Data +public class CategoryRequestDto { + private String name; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dto/request/ProductRequestDto.java b/src/main/java/mate/academy/springboot/datajpa/dto/request/ProductRequestDto.java new file mode 100644 index 00000000..a7dfbe7c --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/request/ProductRequestDto.java @@ -0,0 +1,11 @@ +package mate.academy.springboot.datajpa.dto.request; + +import java.math.BigDecimal; +import lombok.Data; + +@Data +public class ProductRequestDto { + private String title; + private BigDecimal price; + private Long categoryId; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dto/response/CategoryResponseDto.java b/src/main/java/mate/academy/springboot/datajpa/dto/response/CategoryResponseDto.java new file mode 100644 index 00000000..a0e83a17 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/response/CategoryResponseDto.java @@ -0,0 +1,9 @@ +package mate.academy.springboot.datajpa.dto.response; + +import lombok.Data; + +@Data +public class CategoryResponseDto { + private Long id; + private String name; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dto/response/ProductResponseDto.java b/src/main/java/mate/academy/springboot/datajpa/dto/response/ProductResponseDto.java new file mode 100644 index 00000000..5c7d0b32 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/response/ProductResponseDto.java @@ -0,0 +1,12 @@ +package mate.academy.springboot.datajpa.dto.response; + +import java.math.BigDecimal; +import lombok.Data; + +@Data +public class ProductResponseDto { + private Long id; + private String title; + private BigDecimal price; + private Long categoryId; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/model/Category.java b/src/main/java/mate/academy/springboot/datajpa/model/Category.java new file mode 100644 index 00000000..9746772f --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/model/Category.java @@ -0,0 +1,18 @@ +package mate.academy.springboot.datajpa.model; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.Data; + +@Entity +@Data +@Table(name = "categories") +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/model/Product.java b/src/main/java/mate/academy/springboot/datajpa/model/Product.java new file mode 100644 index 00000000..88955d91 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/model/Product.java @@ -0,0 +1,23 @@ +package mate.academy.springboot.datajpa.model; + +import java.math.BigDecimal; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import lombok.Data; + +@Entity +@Data +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; + private BigDecimal price; + @OneToOne + @JoinColumn(name = "category_id") + private Category category; +} diff --git a/src/main/java/mate/academy/springboot/datajpa/repository/CategoryRepository.java b/src/main/java/mate/academy/springboot/datajpa/repository/CategoryRepository.java new file mode 100644 index 00000000..5e9e27db --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/repository/CategoryRepository.java @@ -0,0 +1,11 @@ +package mate.academy.springboot.datajpa.repository; + +import java.util.List; +import mate.academy.springboot.datajpa.model.Category; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CategoryRepository extends JpaRepository { + List getCategoriesByNameIn(List names); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/repository/ProductRepository.java b/src/main/java/mate/academy/springboot/datajpa/repository/ProductRepository.java new file mode 100644 index 00000000..9978c62e --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/repository/ProductRepository.java @@ -0,0 +1,16 @@ +package mate.academy.springboot.datajpa.repository; + +import java.math.BigDecimal; +import java.util.List; +import mate.academy.springboot.datajpa.model.Product; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductRepository extends JpaRepository { + List findAllByPriceBetween(BigDecimal from, BigDecimal to); + + @Query("from Product where category.id in :categoryId") + List findAllByCategoryId(List categoryId); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/CategoryService.java b/src/main/java/mate/academy/springboot/datajpa/services/CategoryService.java new file mode 100644 index 00000000..ded116ba --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/CategoryService.java @@ -0,0 +1,14 @@ +package mate.academy.springboot.datajpa.services; + +import java.util.List; +import mate.academy.springboot.datajpa.model.Category; + +public interface CategoryService { + Category create(Category category); + + Category getById(Long id); + + void deleteById(Long id); + + List getCategoriesByNameIn(List names); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/CategoryServiceImpl.java b/src/main/java/mate/academy/springboot/datajpa/services/CategoryServiceImpl.java new file mode 100644 index 00000000..3cbd57a7 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/CategoryServiceImpl.java @@ -0,0 +1,36 @@ +package mate.academy.springboot.datajpa.services; + +import java.util.List; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.repository.CategoryRepository; +import org.springframework.stereotype.Service; + +@Service +public class CategoryServiceImpl implements CategoryService { + private CategoryRepository categoryRepository; + + public CategoryServiceImpl(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + + @Override + public Category create(Category category) { + return categoryRepository.save(category); + } + + @Override + public Category getById(Long id) { + return categoryRepository.findById(id).orElseThrow( + () -> new RuntimeException("Can`t find category with id: " + id)); + } + + @Override + public void deleteById(Long id) { + categoryRepository.deleteById(id); + } + + @Override + public List getCategoriesByNameIn(List names) { + return categoryRepository.getCategoriesByNameIn(names); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/ProductService.java b/src/main/java/mate/academy/springboot/datajpa/services/ProductService.java new file mode 100644 index 00000000..b2fdf88c --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/ProductService.java @@ -0,0 +1,17 @@ +package mate.academy.springboot.datajpa.services; + +import java.math.BigDecimal; +import java.util.List; +import mate.academy.springboot.datajpa.model.Product; + +public interface ProductService { + Product create(Product product); + + Product getById(Long id); + + void deleteById(Long id); + + List findAllByPriceBetween(BigDecimal from, BigDecimal to); + + List getAllWithCategories(List categoryId); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/ProductServiceImpl.java b/src/main/java/mate/academy/springboot/datajpa/services/ProductServiceImpl.java new file mode 100644 index 00000000..15d9ac4b --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/ProductServiceImpl.java @@ -0,0 +1,42 @@ +package mate.academy.springboot.datajpa.services; + +import java.math.BigDecimal; +import java.util.List; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.repository.ProductRepository; +import org.springframework.stereotype.Service; + +@Service +public class ProductServiceImpl implements ProductService { + private ProductRepository productRepository; + + public ProductServiceImpl(ProductRepository productRepository) { + this.productRepository = productRepository; + } + + @Override + public Product create(Product product) { + return productRepository.save(product); + } + + @Override + public Product getById(Long id) { + return productRepository.findById(id).orElseThrow( + () -> new RuntimeException("Can`t find product by id: " + id)); + } + + @Override + public void deleteById(Long id) { + productRepository.deleteById(id); + } + + @Override + public List findAllByPriceBetween(BigDecimal from, BigDecimal to) { + return productRepository.findAllByPriceBetween(from, to); + } + + @Override + public List getAllWithCategories(List categories) { + return productRepository.findAllByCategoryId(categories); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/mapper/CategoryMapper.java b/src/main/java/mate/academy/springboot/datajpa/services/mapper/CategoryMapper.java new file mode 100644 index 00000000..91f37497 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/mapper/CategoryMapper.java @@ -0,0 +1,22 @@ +package mate.academy.springboot.datajpa.services.mapper; + +import mate.academy.springboot.datajpa.dto.request.CategoryRequestDto; +import mate.academy.springboot.datajpa.dto.response.CategoryResponseDto; +import mate.academy.springboot.datajpa.model.Category; +import org.springframework.stereotype.Component; + +@Component +public class CategoryMapper { + public CategoryResponseDto toDto(Category category) { + CategoryResponseDto categoryResponseDto = new CategoryResponseDto(); + categoryResponseDto.setId(category.getId()); + categoryResponseDto.setName(category.getName()); + return categoryResponseDto; + } + + public Category toModel(CategoryRequestDto categoryRequestDto) { + Category category = new Category(); + category.setName(categoryRequestDto.getName()); + return category; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/services/mapper/ProductMapper.java b/src/main/java/mate/academy/springboot/datajpa/services/mapper/ProductMapper.java new file mode 100644 index 00000000..87d13792 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/services/mapper/ProductMapper.java @@ -0,0 +1,31 @@ +package mate.academy.springboot.datajpa.services.mapper; + +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dto.request.ProductRequestDto; +import mate.academy.springboot.datajpa.dto.response.ProductResponseDto; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.services.CategoryService; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ProductMapper { + private final CategoryService categoryService; + + public ProductResponseDto toDto(Product product) { + ProductResponseDto productResponseDto = new ProductResponseDto(); + productResponseDto.setId(product.getId()); + productResponseDto.setTitle(product.getTitle()); + productResponseDto.setPrice(product.getPrice()); + productResponseDto.setCategoryId(product.getCategory().getId()); + return productResponseDto; + } + + public Product toModel(ProductRequestDto productRequestDto) { + Product product = new Product(); + product.setCategory(categoryService.getById(productRequestDto.getCategoryId())); + product.setPrice(productRequestDto.getPrice()); + product.setTitle(productRequestDto.getTitle()); + return product; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..b83c7a86 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,9 @@ - +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.hibernate.ddl-auto=create +spring.jpa.properties.hibernate.format_sql=true +spring.h2.console.enabled=true +spring.mvc.pathmatch.matching-strategy = ANT_PATH_MATCHER