add new api methods to product controller

This commit is contained in:
Denis Savosin
2024-09-30 12:41:29 +07:00
parent f12839a15f
commit 27595e08dc
11 changed files with 83 additions and 33 deletions

View File

@@ -0,0 +1,16 @@
```shell
curl --request GET \
--url 'http://localhost:8080/api/product/179cffdc-90f8-4627-985d-3d9c88dff5d7'
```
```shell
curl --request POST \
--url http://localhost:8080/api/product \
-H "Content-Type: application/json" \
-d '{"name":"product-tree","description":"some other product","price":30000}'
```
```shell
curl --request DELETE \
--url 'http://localhost:8080/api/product/179cffdc-90f8-4627-985d-3d9c88dff5d7'
```

View File

@@ -1,16 +1,23 @@
package com.example.demo package com.example.demo
import com.example.demo.provider.MockedShopProvider import com.example.demo.provider.MockedShopProvider
import com.example.demo.provider.ProductRepository
import com.example.demo.provider.ShopProvider import com.example.demo.provider.ShopProvider
import com.example.demo.services.ProductService
import com.example.demo.services.ProductServiceImpl
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
@Configuration @Configuration
@EnableJpaRepositories(basePackages = ["com.example.demo.providers"])
class AppConfig { class AppConfig {
@Bean @Bean
fun shopProvider(): ShopProvider{ fun shopProvider(): ShopProvider{
return MockedShopProvider() return MockedShopProvider()
} }
@Bean
fun productService(@Autowired productRepository: ProductRepository): ProductService {
return ProductServiceImpl(productRepository = productRepository)
}
} }

View File

@@ -1,50 +1,56 @@
package com.example.demo.controllers package com.example.demo.controllers
import com.example.demo.exceptions.NotFoundException import com.example.demo.exceptions.NotFoundException
import com.example.demo.models.Product import com.example.demo.exceptions.UnprocessableException
import com.example.demo.provider.ProductRepository
import com.example.demo.requests.CreateProductRequest import com.example.demo.requests.CreateProductRequest
import com.example.demo.responses.makeOkResponse
import com.example.demo.services.ProductService
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.encodeToJsonElement
import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.* import org.springframework.web.bind.annotation.*
import java.time.OffsetDateTime
import java.util.* import java.util.*
@RestController @RestController
@RequestMapping(value = ["/api/product"]) @RequestMapping(value = ["/api/product"], produces = [MediaType.APPLICATION_JSON_VALUE])
class ProductController( class ProductController(
@Autowired val productRepository: ProductRepository val productService: ProductService
) { ) {
@GetMapping(value = ["{guid}"], produces = ["application/json"]) @GetMapping("/{guid}")
@ResponseBody @ResponseBody
@Throws(NotFoundException::class)
fun getProduct( fun getProduct(
@PathVariable guid: UUID @PathVariable guid: UUID
): String { ): String {
val product = productRepository.findByGuid(guid = guid) ?: throw NotFoundException() val product = productService.findByGuid(guid = guid) ?: throw NotFoundException()
return Json.encodeToJsonElement(value = product).toString() return Json.encodeToJsonElement(value = product).toString()
} }
@PostMapping(value = [""], consumes = ["application/json"], produces = ["application/json"]) @PostMapping(value = ["/"], consumes = [MediaType.APPLICATION_JSON_VALUE])
@ResponseBody @ResponseBody
fun createProduct( fun createProduct(
@RequestBody product: CreateProductRequest @RequestBody product: CreateProductRequest
): String { ): String {
val productModel = Product( val saved = productService.create(
id = null, product.name,
guid = UUID.randomUUID(), product.price,
name = product.name, product.description,
description = product.description,
price = product.price,
createdAt = OffsetDateTime.now(),
updatedAt = null,
) )
val saved = productRepository.save(productModel)
return Json.encodeToJsonElement(value = saved).toString() return Json.encodeToJsonElement(value = saved).toString()
} }
// todo delete with soft-delete @DeleteMapping("/{guid}")
@ResponseBody
@Throws(NotFoundException::class, UnprocessableException::class)
fun deleteProduct(
@PathVariable guid: UUID,
): ResponseEntity<Any> {
productService.delete(guid)
return ResponseEntity(makeOkResponse(), HttpStatus.OK)
}
} }

View File

@@ -1,6 +1,7 @@
package com.example.demo.exceptions package com.example.demo.exceptions
import com.example.demo.responses.makeNotFound import com.example.demo.responses.makeNotFoundResponse
import com.example.demo.responses.makeUnprocessableResponse
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.ControllerAdvice import org.springframework.web.bind.annotation.ControllerAdvice
@@ -9,5 +10,11 @@ import org.springframework.web.bind.annotation.ExceptionHandler
@ControllerAdvice @ControllerAdvice
class ExceptionHandler { class ExceptionHandler {
@ExceptionHandler(NotFoundException::class) @ExceptionHandler(NotFoundException::class)
fun handleNotFound(): ResponseEntity<Any> = ResponseEntity(makeNotFound(), HttpStatus.NOT_FOUND) fun handleNotFound(): ResponseEntity<Any> = ResponseEntity(makeNotFoundResponse(), HttpStatus.NOT_FOUND)
@ExceptionHandler(UnprocessableException::class)
fun handleUnprocessable(exception: UnprocessableException): ResponseEntity<Any> = ResponseEntity(
makeUnprocessableResponse(exception.message),
HttpStatus.UNPROCESSABLE_ENTITY,
)
} }

View File

@@ -1,3 +1,3 @@
package com.example.demo.exceptions package com.example.demo.exceptions
class NotFoundException: RuntimeException() class NotFoundException: RuntimeException()

View File

@@ -41,6 +41,7 @@ class MockedShopProvider: ShopProvider {
price = (price * 100).toLong(), price = (price * 100).toLong(),
createdAt = OffsetDateTime.now(), createdAt = OffsetDateTime.now(),
updatedAt = null, updatedAt = null,
deletedAt = null,
) )
} }
} }

View File

@@ -1,5 +1,7 @@
package com.example.demo.responses package com.example.demo.responses
class BaseResponse(val status: ResponseStatus) open class BaseResponse(val status: ResponseStatus)
fun makeNotFound(): BaseResponse = BaseResponse(status = ResponseStatus.NOT_FOUND) fun makeOkResponse(): BaseResponse = BaseResponse(status = ResponseStatus.OK)
fun makeNotFoundResponse(): BaseResponse = BaseResponse(status = ResponseStatus.NOT_FOUND)

View File

@@ -3,5 +3,7 @@ package com.example.demo.responses
import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.annotation.JsonValue
enum class ResponseStatus(@JsonValue val status: String) { enum class ResponseStatus(@JsonValue val status: String) {
NOT_FOUND("not found"); OK("ok"),
NOT_FOUND("not found"),
UNPROCESSABLE("unprocessable");
} }

View File

@@ -0,0 +1,7 @@
package com.example.demo.responses
class UnprocessableResponse(
val cause: String,
): BaseResponse(status = ResponseStatus.UNPROCESSABLE)
fun makeUnprocessableResponse(cause: String): UnprocessableResponse = UnprocessableResponse(cause)

View File

@@ -1,8 +1,8 @@
package com.example.demo.controllers package com.example.demo.controllers
import com.example.demo.models.Product import com.example.demo.models.Product
import com.example.demo.provider.ProductRepository
import com.example.demo.responses.ResponseStatus import com.example.demo.responses.ResponseStatus
import com.example.demo.services.ProductService
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.doReturn
@@ -20,7 +20,7 @@ import java.util.*
@WebMvcTest(ProductController::class) @WebMvcTest(ProductController::class)
class ProductControllerTest(@Autowired val mockMvc: MockMvc) { class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
@MockBean @MockBean
private lateinit var productRepository: ProductRepository private lateinit var productService: ProductService
@Test @Test
fun getProduct_success() { fun getProduct_success() {
@@ -34,9 +34,10 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
price = 11130, price = 11130,
createdAt = now, createdAt = now,
updatedAt = null, updatedAt = null,
deletedAt = null,
) )
whenever(productRepository.findByGuid( whenever(productService.findByGuid(
eq(guid), eq(guid),
)) doReturn product )) doReturn product
@@ -54,7 +55,7 @@ class ProductControllerTest(@Autowired val mockMvc: MockMvc) {
fun getProduct_notFound() { fun getProduct_notFound() {
val guid = UUID.randomUUID() val guid = UUID.randomUUID()
whenever(productRepository.findByGuid( whenever(productService.findByGuid(
eq(guid), eq(guid),
)) doReturn null )) doReturn null

View File

@@ -81,9 +81,10 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc) {
guid = UUID.randomUUID(), guid = UUID.randomUUID(),
name = name, name = name,
description = null, description = null,
price = (price * 100).toInt(), price = (price * 100).toLong(),
createdAt = OffsetDateTime.now(), createdAt = OffsetDateTime.now(),
updatedAt = null, updatedAt = null,
deletedAt = null,
) )
} }
} }