From 8fa7bafd76c9579b79fe1579d67678e141e56392 Mon Sep 17 00:00:00 2001 From: Lurldgbodex Date: Sun, 25 Aug 2024 22:22:04 +0100 Subject: [PATCH] feat: implement dashboard for products --- .../controller/DashboardController.java | 38 +++++++++ .../dashboard/service/DashboardService.java | 26 ++++++ .../product/controller/ProductController.java | 11 ++- .../product/dto/GetProductsDTO.java | 9 +++ .../product/dto/ProductCountDto.java | 10 +++ .../product/dto/ProductDTO.java | 7 +- .../product/product_mapper/ProductMapper.java | 2 +- .../product/service/ProductService.java | 6 ++ .../product/service/ProductServiceImpl.java | 58 +++++++++++++- .../user/serviceImpl/UserServiceImpl.java | 2 +- .../service/ProductServiceImplTest.java | 80 +++++++++++++++++++ 11 files changed, 237 insertions(+), 12 deletions(-) create mode 100644 src/main/java/hng_java_boilerplate/dashboard/controller/DashboardController.java create mode 100644 src/main/java/hng_java_boilerplate/dashboard/service/DashboardService.java create mode 100644 src/main/java/hng_java_boilerplate/product/dto/GetProductsDTO.java create mode 100644 src/main/java/hng_java_boilerplate/product/dto/ProductCountDto.java create mode 100644 src/test/java/hng_java_boilerplate/product/service/ProductServiceImplTest.java diff --git a/src/main/java/hng_java_boilerplate/dashboard/controller/DashboardController.java b/src/main/java/hng_java_boilerplate/dashboard/controller/DashboardController.java new file mode 100644 index 00000000..67ce567e --- /dev/null +++ b/src/main/java/hng_java_boilerplate/dashboard/controller/DashboardController.java @@ -0,0 +1,38 @@ +package hng_java_boilerplate.dashboard.controller; + +import hng_java_boilerplate.dashboard.service.DashboardService; +import hng_java_boilerplate.product.dto.GetProductsDTO; +import hng_java_boilerplate.product.dto.ProductCountDto; +import hng_java_boilerplate.product.dto.ProductDTO; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@PreAuthorize("hasRole('SUPER_ADMIN')") +@RequestMapping("/api/v1/dashboards") +@Tag(name = "dashboard", description = "admin dashboard") +public class DashboardController { + private final DashboardService dashboardService; + + @GetMapping("/products") + @Tag(name = "Products", description = "get all the products in the database") + public ResponseEntity getProducts() { + return ResponseEntity.ok(dashboardService.getAllProducts()); + } + + @GetMapping("/products/count") + @Tag(name = "Product count", description = "get the total counts of products") + public ResponseEntity getProductCount() { + return ResponseEntity.ok(dashboardService.getProductCount()); + } + + @GetMapping("/products/{productId}") + @Tag(name = "product", description = "get a product by the product id") + public ResponseEntity getProductById(@PathVariable String productId) { + return ResponseEntity.ok(dashboardService.getProductById(productId)); + } +} diff --git a/src/main/java/hng_java_boilerplate/dashboard/service/DashboardService.java b/src/main/java/hng_java_boilerplate/dashboard/service/DashboardService.java new file mode 100644 index 00000000..da58cc9d --- /dev/null +++ b/src/main/java/hng_java_boilerplate/dashboard/service/DashboardService.java @@ -0,0 +1,26 @@ +package hng_java_boilerplate.dashboard.service; + +import hng_java_boilerplate.product.dto.GetProductsDTO; +import hng_java_boilerplate.product.dto.ProductCountDto; +import hng_java_boilerplate.product.dto.ProductDTO; +import hng_java_boilerplate.product.service.ProductService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class DashboardService { + private final ProductService productService; + + public ProductCountDto getProductCount() { + return productService.getProductsCount(); + } + + public GetProductsDTO getAllProducts() { + return productService.getProducts(); + } + + public ProductDTO getProductById(String productId) { + return productService.getProductById(productId); + } +} diff --git a/src/main/java/hng_java_boilerplate/product/controller/ProductController.java b/src/main/java/hng_java_boilerplate/product/controller/ProductController.java index 74ceb453..e4f20313 100644 --- a/src/main/java/hng_java_boilerplate/product/controller/ProductController.java +++ b/src/main/java/hng_java_boilerplate/product/controller/ProductController.java @@ -1,5 +1,6 @@ package hng_java_boilerplate.product.controller; +import hng_java_boilerplate.product.dto.ProductDTO; import hng_java_boilerplate.product.dto.ProductSearchDTO; import hng_java_boilerplate.product.entity.Product; import hng_java_boilerplate.product.product_mapper.ProductMapper; @@ -15,10 +16,7 @@ import org.springframework.data.domain.PageRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; @@ -69,4 +67,9 @@ public ResponseEntity searchProducts( productSearchDTO.setSuccess(true); return new ResponseEntity<>(productSearchDTO, HttpStatus.OK); } + + @GetMapping("/{productId}") + public ResponseEntity getProductById(@PathVariable String productId) { + return ResponseEntity.ok(productService.getProductById(productId)); + } } diff --git a/src/main/java/hng_java_boilerplate/product/dto/GetProductsDTO.java b/src/main/java/hng_java_boilerplate/product/dto/GetProductsDTO.java new file mode 100644 index 00000000..88e596c2 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/product/dto/GetProductsDTO.java @@ -0,0 +1,9 @@ +package hng_java_boilerplate.product.dto; + +import lombok.Builder; + +import java.util.List; + +@Builder +public record GetProductsDTO(int status_code, String status, List data) { +} diff --git a/src/main/java/hng_java_boilerplate/product/dto/ProductCountDto.java b/src/main/java/hng_java_boilerplate/product/dto/ProductCountDto.java new file mode 100644 index 00000000..8ee79da9 --- /dev/null +++ b/src/main/java/hng_java_boilerplate/product/dto/ProductCountDto.java @@ -0,0 +1,10 @@ +package hng_java_boilerplate.product.dto; + +import lombok.Builder; + +@Builder +public record ProductCountDto(int status_code, String status, CountData data) { + + @Builder + public record CountData(int count) {} +} diff --git a/src/main/java/hng_java_boilerplate/product/dto/ProductDTO.java b/src/main/java/hng_java_boilerplate/product/dto/ProductDTO.java index fc4b439e..ee6fe3f9 100644 --- a/src/main/java/hng_java_boilerplate/product/dto/ProductDTO.java +++ b/src/main/java/hng_java_boilerplate/product/dto/ProductDTO.java @@ -1,18 +1,19 @@ package hng_java_boilerplate.product.dto; -import jakarta.persistence.Column; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +@Data +@Builder @NoArgsConstructor @AllArgsConstructor -@Data public class ProductDTO { private String id; private String name; private String description; private String category; private double price; - private String imageUrl; + private String image_url; } diff --git a/src/main/java/hng_java_boilerplate/product/product_mapper/ProductMapper.java b/src/main/java/hng_java_boilerplate/product/product_mapper/ProductMapper.java index 831fd2bd..e7e4106c 100644 --- a/src/main/java/hng_java_boilerplate/product/product_mapper/ProductMapper.java +++ b/src/main/java/hng_java_boilerplate/product/product_mapper/ProductMapper.java @@ -17,7 +17,7 @@ public interface ProductMapper { @Mapping(source = "name", target = "name") @Mapping(source = "category", target = "category") @Mapping(source = "description", target = "description") - @Mapping(source = "imageUrl", target = "imageUrl") + @Mapping(source = "imageUrl", target = "image_url") ProductDTO toDTO(Product product); default Page toDTOList(Page products) { diff --git a/src/main/java/hng_java_boilerplate/product/service/ProductService.java b/src/main/java/hng_java_boilerplate/product/service/ProductService.java index f27eeba5..441712e1 100644 --- a/src/main/java/hng_java_boilerplate/product/service/ProductService.java +++ b/src/main/java/hng_java_boilerplate/product/service/ProductService.java @@ -1,5 +1,8 @@ package hng_java_boilerplate.product.service; +import hng_java_boilerplate.product.dto.GetProductsDTO; +import hng_java_boilerplate.product.dto.ProductCountDto; +import hng_java_boilerplate.product.dto.ProductDTO; import hng_java_boilerplate.product.entity.Product; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -10,4 +13,7 @@ public interface ProductService { //Method to Search for products with certain criteria, returns a list of products Page productsSearch(String name, String category, Double minPrice, Double maxPrice, Pageable pageable); + ProductCountDto getProductsCount(); + GetProductsDTO getProducts(); + ProductDTO getProductById(String productId); } \ No newline at end of file diff --git a/src/main/java/hng_java_boilerplate/product/service/ProductServiceImpl.java b/src/main/java/hng_java_boilerplate/product/service/ProductServiceImpl.java index abd16c11..cd8561cd 100644 --- a/src/main/java/hng_java_boilerplate/product/service/ProductServiceImpl.java +++ b/src/main/java/hng_java_boilerplate/product/service/ProductServiceImpl.java @@ -1,4 +1,8 @@ package hng_java_boilerplate.product.service; +import hng_java_boilerplate.exception.NotFoundException; +import hng_java_boilerplate.product.dto.GetProductsDTO; +import hng_java_boilerplate.product.dto.ProductCountDto; +import hng_java_boilerplate.product.dto.ProductDTO; import hng_java_boilerplate.product.entity.Product; import hng_java_boilerplate.product.repository.ProductRepository; import org.springframework.data.domain.Page; @@ -7,10 +11,9 @@ import java.util.List; @Service +public class ProductServiceImpl implements ProductService { -public class ProductServiceImpl implements ProductService{ - - private ProductRepository productRepository; + private final ProductRepository productRepository; public ProductServiceImpl(ProductRepository productRepository) { this.productRepository = productRepository; @@ -20,4 +23,53 @@ public ProductServiceImpl(ProductRepository productRepository) { public Page productsSearch(String name, String category, Double minPrice, Double maxPrice, Pageable pageable) { return productRepository.searchProducts(name, category, minPrice, maxPrice, pageable); } + + @Override + public ProductCountDto getProductsCount() { + List products = productRepository.findAll(); + int productsCount = products.size(); + + ProductCountDto.CountData data = new ProductCountDto + .CountData(productsCount); + + return ProductCountDto + .builder() + .status_code(200) + .status("success") + .data(data) + .build(); + } + + @Override + public GetProductsDTO getProducts() { + List products = productRepository.findAll(); + + List productDTOS = products.stream() + .map(this::mapToProductDto) + .toList(); + + return GetProductsDTO.builder() + .status_code(200) + .status("success") + .data(productDTOS) + .build(); + } + + @Override + public ProductDTO getProductById(String productId) { + Product product = productRepository.findById(productId) + .orElseThrow(() -> new NotFoundException("product not found with id: " + productId)); + return mapToProductDto(product); + } + + private ProductDTO mapToProductDto(Product product) { + return ProductDTO.builder() + .id(product.getId()) + .name(product.getName()) + .price(product.getPrice()) + .description(product.getDescription()) + .category(product.getCategory()) + .image_url(product.getImageUrl()) + .build(); + } } diff --git a/src/main/java/hng_java_boilerplate/user/serviceImpl/UserServiceImpl.java b/src/main/java/hng_java_boilerplate/user/serviceImpl/UserServiceImpl.java index b04fa2e4..eede7265 100644 --- a/src/main/java/hng_java_boilerplate/user/serviceImpl/UserServiceImpl.java +++ b/src/main/java/hng_java_boilerplate/user/serviceImpl/UserServiceImpl.java @@ -270,7 +270,7 @@ private GetUserDto convertUserToGetUserDto(User user) { .build(); } - // Save method for User entity + // Save method for User controller @Override public User save(User user) { return userRepository.save(user); diff --git a/src/test/java/hng_java_boilerplate/product/service/ProductServiceImplTest.java b/src/test/java/hng_java_boilerplate/product/service/ProductServiceImplTest.java new file mode 100644 index 00000000..8cc4de2d --- /dev/null +++ b/src/test/java/hng_java_boilerplate/product/service/ProductServiceImplTest.java @@ -0,0 +1,80 @@ +package hng_java_boilerplate.product.service; + +import hng_java_boilerplate.product.dto.GetProductsDTO; +import hng_java_boilerplate.product.dto.ProductCountDto; +import hng_java_boilerplate.product.dto.ProductDTO; +import hng_java_boilerplate.product.entity.Product; +import hng_java_boilerplate.product.repository.ProductRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ProductServiceImplTest { + @Mock + private ProductRepository productRepository; + @InjectMocks + private ProductServiceImpl underTest; + + private Product product; + + @BeforeEach + void setUp() { + product = new Product(); + product.setId("prod-id"); + product.setName("product"); + product.setDescription("prod-desc"); + product.setCategory("prod-cat"); + } + + + @Test + void shouldGetProductsCount() { + when(productRepository.findAll()).thenReturn(List.of(product)); + + ProductCountDto response = underTest.getProductsCount(); + + assertThat(response.status_code()).isEqualTo(200); + assertThat(response.status()).isEqualTo("success"); + assertThat(response.data()).hasFieldOrPropertyWithValue("count", 1); + + verify(productRepository).findAll(); + } + + @Test + void getProducts() { + when(productRepository.findAll()).thenReturn(List.of(product)); + GetProductsDTO products = underTest.getProducts(); + + assertThat(products.status_code()).isEqualTo(200); + assertThat(products.status()).isEqualTo("success"); + assertThat(products.data()).hasSize(1); + assertThat(products.data().get(0)).hasFieldOrPropertyWithValue("id", product.getId()); + assertThat(products.data().get(0)).hasFieldOrPropertyWithValue("name", product.getName()); + + verify(productRepository).findAll(); + } + + @Test + void getProductById() { + when(productRepository.findById("prod-id")).thenReturn(Optional.of(product)); + + ProductDTO response = underTest.getProductById("prod-id"); + + assertThat(response.getId()).isEqualTo(product.getId()); + assertThat(response.getName()).isEqualTo(product.getName()); + assertThat(response.getDescription()).isEqualTo(product.getDescription()); + + verify(productRepository).findById("prod-id"); + } +} \ No newline at end of file