mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-26 00:32:34 +03:00
add new api methods to product controller
This commit is contained in:
16
doc/examples/api/product.md
Normal file
16
doc/examples/api/product.md
Normal 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'
|
||||||
|
```
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user