mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add get products api
This commit is contained in:
@@ -10,18 +10,25 @@ import com.example.demo.services.database.product.ProductService
|
|||||||
import com.example.demo.services.database.product.ProductServiceImpl
|
import com.example.demo.services.database.product.ProductServiceImpl
|
||||||
import com.example.demo.services.kafka.Producer
|
import com.example.demo.services.kafka.Producer
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.beans.factory.annotation.Value
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.context.annotation.Bean
|
import org.springframework.context.annotation.Bean
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
class AppConfig(
|
class AppConfig(
|
||||||
@Value("\${kafka.producer.product.default-sync-topic}")
|
@Value("\${kafka.producer.product.default-sync-topic}")
|
||||||
private val defaultProductSyncTopic: String
|
private val defaultProductSyncTopic: String
|
||||||
) {
|
) {
|
||||||
@Bean
|
@Bean
|
||||||
fun objectMapper(): ObjectMapper = ObjectMapper()
|
fun objectMapper(): ObjectMapper {
|
||||||
|
val objectMapper = ObjectMapper()
|
||||||
|
objectMapper.registerModules(JavaTimeModule())
|
||||||
|
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
|
||||||
|
|
||||||
|
return objectMapper
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun shopProvider(): ShopProvider = MockedShopProvider()
|
fun shopProvider(): ShopProvider = MockedShopProvider()
|
||||||
@@ -39,3 +46,4 @@ class AppConfig(
|
|||||||
@Bean
|
@Bean
|
||||||
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import com.example.demo.services.database.product.ProductService
|
|||||||
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
||||||
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
||||||
import jakarta.validation.Valid
|
import jakarta.validation.Valid
|
||||||
|
import org.springdoc.core.annotations.ParameterObject
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.http.HttpStatus
|
import org.springframework.http.HttpStatus
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
@@ -21,7 +23,6 @@ class ProductController(
|
|||||||
val productService: ProductService,
|
val productService: ProductService,
|
||||||
) {
|
) {
|
||||||
@GetMapping("/{guid}")
|
@GetMapping("/{guid}")
|
||||||
@ResponseBody
|
|
||||||
@Throws(NotFoundException::class)
|
@Throws(NotFoundException::class)
|
||||||
fun getProduct(
|
fun getProduct(
|
||||||
@PathVariable guid: UUID,
|
@PathVariable guid: UUID,
|
||||||
@@ -31,8 +32,25 @@ class ProductController(
|
|||||||
return ResponseEntity(product, HttpStatus.OK)
|
return ResponseEntity(product, HttpStatus.OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("")
|
||||||
|
fun getProducts(
|
||||||
|
@ParameterObject pageable: Pageable,
|
||||||
|
): ResponseEntity<Any> {
|
||||||
|
val products = productService.findAll(pageable)
|
||||||
|
|
||||||
|
return ResponseEntity(
|
||||||
|
mapOf(
|
||||||
|
"data" to products.content,
|
||||||
|
"meta" to mapOf(
|
||||||
|
"total" to products.totalElements,
|
||||||
|
"pages" to products.totalPages,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
HttpStatus.OK,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/{guid}/sync")
|
@PostMapping("/{guid}/sync")
|
||||||
@ResponseBody
|
|
||||||
@Throws(NotFoundException::class)
|
@Throws(NotFoundException::class)
|
||||||
fun syncProductToKafka(
|
fun syncProductToKafka(
|
||||||
@PathVariable guid: UUID,
|
@PathVariable guid: UUID,
|
||||||
@@ -48,7 +66,6 @@ class ProductController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = [""], consumes = [MediaType.APPLICATION_JSON_VALUE])
|
@PostMapping(value = [""], consumes = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
@ResponseBody
|
|
||||||
fun createProduct(
|
fun createProduct(
|
||||||
@Valid @RequestBody product: CreateProductRequest,
|
@Valid @RequestBody product: CreateProductRequest,
|
||||||
): ResponseEntity<Any> {
|
): ResponseEntity<Any> {
|
||||||
@@ -62,7 +79,6 @@ class ProductController(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{guid}")
|
@DeleteMapping("/{guid}")
|
||||||
@ResponseBody
|
|
||||||
@Throws(NotFoundException::class, UnprocessableException::class)
|
@Throws(NotFoundException::class, UnprocessableException::class)
|
||||||
fun deleteProduct(
|
fun deleteProduct(
|
||||||
@PathVariable guid: UUID,
|
@PathVariable guid: UUID,
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ package com.example.demo.providers
|
|||||||
import com.example.demo.models.Product
|
import com.example.demo.models.Product
|
||||||
import org.springframework.data.jdbc.repository.query.Query
|
import org.springframework.data.jdbc.repository.query.Query
|
||||||
import org.springframework.data.repository.CrudRepository
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.data.repository.PagingAndSortingRepository
|
||||||
import org.springframework.data.repository.query.Param
|
import org.springframework.data.repository.query.Param
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
interface ProductRepository: CrudRepository<Product, Long> {
|
interface ProductRepository: CrudRepository<Product, Long>, PagingAndSortingRepository<Product, Long> {
|
||||||
fun findByGuid(guid: UUID): Product?
|
fun findByGuid(guid: UUID): Product?
|
||||||
|
|
||||||
@Query(value = "UPDATE Product SET deleted_at = :deletedAt WHERE guid = :guid RETURNING *")
|
@Query(value = "UPDATE Product SET deleted_at = :deletedAt WHERE guid = :guid RETURNING *")
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.example.demo.models.Product
|
|||||||
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
||||||
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
||||||
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
import com.example.demo.services.kafka.exceptions.InvalidArgumentException
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -11,6 +13,8 @@ import java.util.*
|
|||||||
interface ProductService {
|
interface ProductService {
|
||||||
fun findByGuid(guid: UUID): Product?
|
fun findByGuid(guid: UUID): Product?
|
||||||
|
|
||||||
|
fun findAll(pageable: Pageable): Page<Product>
|
||||||
|
|
||||||
fun create(name: String, price: Long, description: String?): Product
|
fun create(name: String, price: Long, description: String?): Product
|
||||||
|
|
||||||
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
@Throws(ProductNotFoundException::class, AlreadyDeletedException::class)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.example.demo.providers.ProductRepository
|
|||||||
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
||||||
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
import com.example.demo.services.database.product.exceptions.ProductNotFoundException
|
||||||
import com.example.demo.services.kafka.Producer
|
import com.example.demo.services.kafka.Producer
|
||||||
|
import org.springframework.data.domain.Page
|
||||||
|
import org.springframework.data.domain.Pageable
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -15,6 +17,8 @@ class ProductServiceImpl(
|
|||||||
): ProductService {
|
): ProductService {
|
||||||
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)
|
override fun findByGuid(guid: UUID): Product? = productRepository.findByGuid(guid)
|
||||||
|
|
||||||
|
override fun findAll(pageable: Pageable): Page<Product> = productRepository.findAll(pageable)
|
||||||
|
|
||||||
override fun create(name: String, price: Long, description: String?): Product {
|
override fun create(name: String, price: Long, description: String?): Product {
|
||||||
val product = Product(
|
val product = Product(
|
||||||
id = null,
|
id = null,
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||||||
import org.hamcrest.Matchers.contains
|
import org.hamcrest.Matchers.contains
|
||||||
import org.hamcrest.Matchers.nullValue
|
import org.hamcrest.Matchers.nullValue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
import org.mockito.kotlin.eq
|
import org.mockito.kotlin.eq
|
||||||
import org.mockito.kotlin.verifyNoInteractions
|
import org.mockito.kotlin.verifyNoInteractions
|
||||||
import org.mockito.kotlin.whenever
|
import org.mockito.kotlin.whenever
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
|
||||||
import org.springframework.boot.test.mock.mockito.MockBean
|
import org.springframework.boot.test.mock.mockito.MockBean
|
||||||
|
import org.springframework.data.domain.PageImpl
|
||||||
|
import org.springframework.data.domain.PageRequest
|
||||||
|
import org.springframework.data.domain.Sort
|
||||||
import org.springframework.http.MediaType
|
import org.springframework.http.MediaType
|
||||||
import org.springframework.test.web.servlet.MockMvc
|
import org.springframework.test.web.servlet.MockMvc
|
||||||
import org.springframework.test.web.servlet.delete
|
import org.springframework.test.web.servlet.delete
|
||||||
@@ -76,6 +80,36 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
|
|||||||
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
|
.andExpect { jsonPath("\$.status") { value(ResponseStatus.NOT_FOUND.status) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getProducts_success() {
|
||||||
|
val now = OffsetDateTime.now()
|
||||||
|
whenever(productService.findAll(
|
||||||
|
PageRequest.of(1, 2, Sort.by(Sort.Direction.DESC, "createdAt")),
|
||||||
|
)) doReturn PageImpl(listOf(Product(
|
||||||
|
id = 12,
|
||||||
|
guid = UUID.randomUUID(),
|
||||||
|
name = "some",
|
||||||
|
description = null,
|
||||||
|
price = 11130,
|
||||||
|
createdAt = now,
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)))
|
||||||
|
|
||||||
|
mockMvc.get("/api/product?page=1&size=2&sort=created_at,desc")
|
||||||
|
.andExpect { status { status { isOk() } } }
|
||||||
|
.andExpect { content { contentType(MediaType.APPLICATION_JSON) } }
|
||||||
|
.andExpect { jsonPath("\$.meta.total") { value(1) } }
|
||||||
|
.andExpect { jsonPath("\$.meta.pages") { value(1) } }
|
||||||
|
.andExpect { jsonPath("\$.data") { isArray() } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].id") { value(12) } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].name") { value("some") } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].description") { value(null) } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].createdAt") { value(now.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].priceDouble") { value(111.30) } }
|
||||||
|
.andExpect { jsonPath("\$.data[0].isDeleted") { value(false) } }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun createProduct_success() {
|
fun createProduct_success() {
|
||||||
val productId = 13.toLong()
|
val productId = 13.toLong()
|
||||||
|
|||||||
Reference in New Issue
Block a user