diff --git a/pom.xml b/pom.xml index cf704585..6f47656d 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,33 @@ org.springframework.boot spring-boot-autoconfigure + + org.springframework.boot + spring-boot-starter-data-jpa + + + com.h2database + h2 + runtime + + + org.projectlombok + lombok + 1.18.28 + provided + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-starter-validation + 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..4512a708 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/controller/CategoryController.java @@ -0,0 +1,52 @@ +package mate.academy.springboot.datajpa.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dto.request.CategoryRequestDto; +import mate.academy.springboot.datajpa.dto.response.CategoryResponseDto; +import mate.academy.springboot.datajpa.mapper.RequestDtoMapper; +import mate.academy.springboot.datajpa.mapper.ResponseDtoMapper; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.service.CategoryService; +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 ResponseDtoMapper responseDtoMapper; + private final RequestDtoMapper requestDtoMapper; + + @PostMapping + public CategoryResponseDto create(@RequestBody @Valid CategoryRequestDto requestDto) { + Category category = categoryService.create(requestDtoMapper + .mapToModel(requestDto)); + return responseDtoMapper.mapToDto(category); + } + + @GetMapping("/{id}") + public CategoryResponseDto get(@PathVariable Long id) { + Category category = categoryService.getById(id); + return responseDtoMapper.mapToDto(category); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + categoryService.delete(id); + } + + @PutMapping("/{id}") + public CategoryResponseDto update(@RequestBody @Valid CategoryRequestDto requestDto, + @PathVariable Long id) { + Category category = categoryService.update(requestDtoMapper.mapToModel(requestDto), id); + return responseDtoMapper.mapToDto(category); + } +} 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..6982d817 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/controller/ProductController.java @@ -0,0 +1,73 @@ +package mate.academy.springboot.datajpa.controller; + +import jakarta.validation.Valid; +import java.math.BigDecimal; +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.mapper.RequestDtoMapper; +import mate.academy.springboot.datajpa.mapper.ResponseDtoMapper; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.service.ProductService; +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.RestController; + +@RestController +@RequestMapping("/products") +@RequiredArgsConstructor +public class ProductController { + private final ProductService productService; + private final ResponseDtoMapper responseDtoMapper; + private final RequestDtoMapper requestDtoMapper; + + @PostMapping + public ProductResponseDto create(@RequestBody @Valid ProductRequestDto productRequestDto) { + Product product = productService.create(requestDtoMapper.mapToModel(productRequestDto)); + return responseDtoMapper.mapToDto(product); + } + + @GetMapping("/{id}") + public ProductResponseDto getById(@PathVariable Long id) { + Product product = productService.getById(id); + return responseDtoMapper.mapToDto(product); + } + + @DeleteMapping("/{id}") + public void delete(@PathVariable Long id) { + productService.delete(id); + } + + @PutMapping("/{id}") + public ProductResponseDto update(@PathVariable Long id, + @RequestBody @Valid ProductRequestDto productRequestDto) { + Product product = productService.update(requestDtoMapper + .mapToModel(productRequestDto), id); + return responseDtoMapper.mapToDto(product); + } + + @GetMapping("/price") + public List getAllByPriceBetween(@RequestParam BigDecimal from, + @RequestParam BigDecimal to) { + List products = productService.getAllByPriceBetween(from, to); + return products.stream() + .map(responseDtoMapper::mapToDto) + .collect(Collectors.toList()); + } + + @GetMapping("/category") + public List getAllByCategories(@RequestParam List categoryNames) { + List products = productService.getAllByCategoryNames(categoryNames); + return products.stream() + .map(responseDtoMapper::mapToDto) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dao/CategoryRepository.java b/src/main/java/mate/academy/springboot/datajpa/dao/CategoryRepository.java new file mode 100644 index 00000000..2f05c152 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dao/CategoryRepository.java @@ -0,0 +1,9 @@ +package mate.academy.springboot.datajpa.dao; + +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 { +} diff --git a/src/main/java/mate/academy/springboot/datajpa/dao/ProductRepository.java b/src/main/java/mate/academy/springboot/datajpa/dao/ProductRepository.java new file mode 100644 index 00000000..99b0d757 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dao/ProductRepository.java @@ -0,0 +1,14 @@ +package mate.academy.springboot.datajpa.dao; + +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.stereotype.Repository; + +@Repository +public interface ProductRepository extends JpaRepository { + List findAllByPriceBetween(BigDecimal from, BigDecimal to); + + List findByCategoryNameIn(List categoryNames); +} 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..67ab1767 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/request/CategoryRequestDto.java @@ -0,0 +1,12 @@ +package mate.academy.springboot.datajpa.dto.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CategoryRequestDto { + @NotNull + 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..7ead3b3e --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/request/ProductRequestDto.java @@ -0,0 +1,19 @@ +package mate.academy.springboot.datajpa.dto.request; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import java.math.BigDecimal; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class ProductRequestDto { + @NotNull + private String title; + @Positive + @NotNull + private BigDecimal price; + @NotNull + 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..56fd0b65 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/response/CategoryResponseDto.java @@ -0,0 +1,11 @@ +package mate.academy.springboot.datajpa.dto.response; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +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..1ed6b0d4 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/dto/response/ProductResponseDto.java @@ -0,0 +1,14 @@ +package mate.academy.springboot.datajpa.dto.response; + +import java.math.BigDecimal; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +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/mapper/RequestDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/RequestDtoMapper.java new file mode 100644 index 00000000..92248eea --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/RequestDtoMapper.java @@ -0,0 +1,5 @@ +package mate.academy.springboot.datajpa.mapper; + +public interface RequestDtoMapper { + T mapToModel(D dto); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/mapper/ResponseDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/ResponseDtoMapper.java new file mode 100644 index 00000000..86792512 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/ResponseDtoMapper.java @@ -0,0 +1,5 @@ +package mate.academy.springboot.datajpa.mapper; + +public interface ResponseDtoMapper { + D mapToDto(T model); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryRequestDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryRequestDtoMapper.java new file mode 100644 index 00000000..a62fdeb3 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryRequestDtoMapper.java @@ -0,0 +1,16 @@ +package mate.academy.springboot.datajpa.mapper.impl; + +import mate.academy.springboot.datajpa.dto.request.CategoryRequestDto; +import mate.academy.springboot.datajpa.mapper.RequestDtoMapper; +import mate.academy.springboot.datajpa.model.Category; +import org.springframework.stereotype.Component; + +@Component +public class CategoryRequestDtoMapper implements RequestDtoMapper { + @Override + public Category mapToModel(CategoryRequestDto dto) { + Category category = new Category(); + category.setName(dto.getName()); + return category; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryResponseDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryResponseDtoMapper.java new file mode 100644 index 00000000..2d545395 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/CategoryResponseDtoMapper.java @@ -0,0 +1,17 @@ +package mate.academy.springboot.datajpa.mapper.impl; + +import mate.academy.springboot.datajpa.dto.response.CategoryResponseDto; +import mate.academy.springboot.datajpa.mapper.ResponseDtoMapper; +import mate.academy.springboot.datajpa.model.Category; +import org.springframework.stereotype.Component; + +@Component +public class CategoryResponseDtoMapper implements ResponseDtoMapper { + @Override + public CategoryResponseDto mapToDto(Category category) { + CategoryResponseDto categoryResponseDto = new CategoryResponseDto(); + categoryResponseDto.setId(category.getId()); + categoryResponseDto.setName(category.getName()); + return categoryResponseDto; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductRequestDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductRequestDtoMapper.java new file mode 100644 index 00000000..3dd5dd1c --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductRequestDtoMapper.java @@ -0,0 +1,19 @@ +package mate.academy.springboot.datajpa.mapper.impl; + +import mate.academy.springboot.datajpa.dto.request.ProductRequestDto; +import mate.academy.springboot.datajpa.mapper.RequestDtoMapper; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.model.Product; +import org.springframework.stereotype.Component; + +@Component +public class ProductRequestDtoMapper implements RequestDtoMapper { + @Override + public Product mapToModel(ProductRequestDto dto) { + Product product = new Product(); + product.setTitle(dto.getTitle()); + product.setPrice(dto.getPrice()); + product.setCategory(new Category(dto.getCategoryId())); + return product; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductResponseDtoMapper.java b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductResponseDtoMapper.java new file mode 100644 index 00000000..93a195ec --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/mapper/impl/ProductResponseDtoMapper.java @@ -0,0 +1,19 @@ +package mate.academy.springboot.datajpa.mapper.impl; + +import mate.academy.springboot.datajpa.dto.response.ProductResponseDto; +import mate.academy.springboot.datajpa.mapper.ResponseDtoMapper; +import mate.academy.springboot.datajpa.model.Product; +import org.springframework.stereotype.Component; + +@Component +public class ProductResponseDtoMapper implements ResponseDtoMapper { + @Override + public ProductResponseDto mapToDto(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; + } +} 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..588e76c1 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/model/Category.java @@ -0,0 +1,28 @@ +package mate.academy.springboot.datajpa.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Table(name = "categories") +@Setter +@Getter +@NoArgsConstructor +@ToString +public class Category { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String name; + + public Category(Long id) { + this.id = id; + } +} 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..7d8e0365 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/model/Product.java @@ -0,0 +1,35 @@ +package mate.academy.springboot.datajpa.model; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Table(name = "products") +@Setter +@Getter +public class Product { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; + private BigDecimal price; + @ManyToOne(fetch = FetchType.LAZY) + private Category category; + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", title='" + title + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/service/CategoryService.java b/src/main/java/mate/academy/springboot/datajpa/service/CategoryService.java new file mode 100644 index 00000000..c7e11c9f --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/service/CategoryService.java @@ -0,0 +1,13 @@ +package mate.academy.springboot.datajpa.service; + +import mate.academy.springboot.datajpa.model.Category; + +public interface CategoryService { + Category create(Category category); + + Category getById(Long id); + + void delete(Long id); + + Category update(Category category, Long id); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/service/ProductService.java b/src/main/java/mate/academy/springboot/datajpa/service/ProductService.java new file mode 100644 index 00000000..039586ed --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/service/ProductService.java @@ -0,0 +1,19 @@ +package mate.academy.springboot.datajpa.service; + +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 delete(Long id); + + Product update(Product product, Long id); + + List getAllByPriceBetween(BigDecimal from, BigDecimal to); + + List getAllByCategoryNames(List categoryNames); +} diff --git a/src/main/java/mate/academy/springboot/datajpa/service/impl/CategoryServiceImpl.java b/src/main/java/mate/academy/springboot/datajpa/service/impl/CategoryServiceImpl.java new file mode 100644 index 00000000..3fe09ef5 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/service/impl/CategoryServiceImpl.java @@ -0,0 +1,38 @@ +package mate.academy.springboot.datajpa.service.impl; + +import java.util.NoSuchElementException; +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dao.CategoryRepository; +import mate.academy.springboot.datajpa.model.Category; +import mate.academy.springboot.datajpa.service.CategoryService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CategoryServiceImpl implements CategoryService { + private final CategoryRepository categoryRepository; + + @Override + public Category create(Category category) { + return categoryRepository.save(category); + } + + @Override + public Category getById(Long id) { + return categoryRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("Can't find the category by " + + "id: " + id)); + } + + @Override + public void delete(Long id) { + categoryRepository.deleteById(id); + } + + @Override + public Category update(Category category, Long id) { + Category oldCategory = getById(id); + oldCategory.setName(category.getName()); + return categoryRepository.save(oldCategory); + } +} diff --git a/src/main/java/mate/academy/springboot/datajpa/service/impl/ProductServiceImpl.java b/src/main/java/mate/academy/springboot/datajpa/service/impl/ProductServiceImpl.java new file mode 100644 index 00000000..a32ed3e4 --- /dev/null +++ b/src/main/java/mate/academy/springboot/datajpa/service/impl/ProductServiceImpl.java @@ -0,0 +1,51 @@ +package mate.academy.springboot.datajpa.service.impl; + +import java.math.BigDecimal; +import java.util.List; +import java.util.NoSuchElementException; +import lombok.RequiredArgsConstructor; +import mate.academy.springboot.datajpa.dao.ProductRepository; +import mate.academy.springboot.datajpa.model.Product; +import mate.academy.springboot.datajpa.service.ProductService; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ProductServiceImpl implements ProductService { + private final ProductRepository productRepository; + + @Override + public Product create(Product product) { + return productRepository.save(product); + } + + @Override + public Product getById(Long id) { + return productRepository.findById(id) + .orElseThrow(() -> new NoSuchElementException("Product not found with id: " + id)); + } + + @Override + public void delete(Long id) { + productRepository.deleteById(id); + } + + @Override + public Product update(Product product, Long id) { + Product oldProduct = getById(id); + oldProduct.setCategory(product.getCategory()); + oldProduct.setTitle(product.getTitle()); + oldProduct.setPrice(product.getPrice()); + return productRepository.save(oldProduct); + } + + @Override + public List getAllByPriceBetween(BigDecimal from, BigDecimal to) { + return productRepository.findAllByPriceBetween(from, to); + } + + @Override + public List getAllByCategoryNames(List categoryNames) { + return productRepository.findByCategoryNameIn(categoryNames); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b137891..78aa24bd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,8 @@ +spring.datasource.url=jdbc:h2:mem:springboot_db +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=user +spring.datasource.password=1234 +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.h2.console.enabled=true +spring.jpa.show-sql=true