mirror of
https://github.com/Dannecron/spring-boot-demo.git
synced 2025-12-25 16:22:35 +03:00
add customer table, dto, repository, service
This commit is contained in:
@@ -2,12 +2,11 @@ package com.example.demo.config
|
|||||||
|
|
||||||
import com.example.demo.config.properties.KafkaProperties
|
import com.example.demo.config.properties.KafkaProperties
|
||||||
import com.example.demo.config.properties.ValidationProperties
|
import com.example.demo.config.properties.ValidationProperties
|
||||||
import com.example.demo.providers.CityRepository
|
import com.example.demo.providers.*
|
||||||
import com.example.demo.providers.MockedShopProvider
|
|
||||||
import com.example.demo.providers.ProductRepository
|
|
||||||
import com.example.demo.providers.ShopProvider
|
|
||||||
import com.example.demo.services.database.city.CityService
|
import com.example.demo.services.database.city.CityService
|
||||||
import com.example.demo.services.database.city.CityServiceImpl
|
import com.example.demo.services.database.city.CityServiceImpl
|
||||||
|
import com.example.demo.services.database.customer.CustomerService
|
||||||
|
import com.example.demo.services.database.customer.CustomerServiceImpl
|
||||||
import com.example.demo.services.database.product.ProductService
|
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
|
||||||
@@ -55,6 +54,12 @@ class AppConfig(
|
|||||||
@Bean
|
@Bean
|
||||||
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
fun cityService(@Autowired cityRepository: CityRepository): CityService = CityServiceImpl(cityRepository)
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
fun customerService(
|
||||||
|
@Autowired customerRepository: CustomerRepository,
|
||||||
|
@Autowired cityRepository: CityRepository,
|
||||||
|
): CustomerService = CustomerServiceImpl(customerRepository, cityRepository)
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
fun schemaValidator(
|
fun schemaValidator(
|
||||||
@Autowired validationProperties: ValidationProperties,
|
@Autowired validationProperties: ValidationProperties,
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
package com.example.demo.models
|
package com.example.demo.models
|
||||||
|
|
||||||
data class Customer(val name: String, val city: City, val orders: List<Order>) {
|
import com.example.demo.services.serializables.OffsetDateTimeSerialization
|
||||||
/**
|
import com.example.demo.services.serializables.UuidSerialization
|
||||||
* Return the most expensive product among all delivered products
|
import kotlinx.serialization.Serializable
|
||||||
*/
|
import org.springframework.data.annotation.Id
|
||||||
fun getMostExpensiveDeliveredProduct(): Product? = orders.filter { ord -> ord.isDelivered }
|
import org.springframework.data.relational.core.mapping.Column
|
||||||
.flatMap { ord -> ord.products }
|
import org.springframework.data.relational.core.mapping.Table
|
||||||
.maxByOrNull { pr -> pr.price }
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun getMostExpensiveOrderedProduct(): Product? = orders.flatMap { ord -> ord.products }.maxByOrNull { pr -> pr.price }
|
@Table("customer")
|
||||||
|
data class Customer(
|
||||||
fun getOrderedProducts(): Set<Product> = orders.flatMap { order -> order.products }.toSet()
|
@Id
|
||||||
|
val id: Long?,
|
||||||
fun getTotalOrderPrice(): Double = orders.flatMap { ord -> ord.products }.sumOf { pr -> pr.getPriceDouble()}
|
@Serializable(with = UuidSerialization::class)
|
||||||
}
|
val guid: UUID,
|
||||||
|
val name: String,
|
||||||
|
val cityId: Long?,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
|
@Column(value = "created_at")
|
||||||
|
val createdAt: OffsetDateTime,
|
||||||
|
@Serializable(with = OffsetDateTimeSerialization::class)
|
||||||
|
@Column(value = "updated_at")
|
||||||
|
val updatedAt: OffsetDateTime?,
|
||||||
|
)
|
||||||
|
|||||||
16
src/main/kotlin/com/example/demo/models/CustomerLocal.kt
Normal file
16
src/main/kotlin/com/example/demo/models/CustomerLocal.kt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.example.demo.models
|
||||||
|
|
||||||
|
data class CustomerLocal(val name: String, val city: City, val orders: List<Order>) {
|
||||||
|
/**
|
||||||
|
* Return the most expensive product among all delivered products
|
||||||
|
*/
|
||||||
|
fun getMostExpensiveDeliveredProduct(): Product? = orders.filter { ord -> ord.isDelivered }
|
||||||
|
.flatMap { ord -> ord.products }
|
||||||
|
.maxByOrNull { pr -> pr.price }
|
||||||
|
|
||||||
|
fun getMostExpensiveOrderedProduct(): Product? = orders.flatMap { ord -> ord.products }.maxByOrNull { pr -> pr.price }
|
||||||
|
|
||||||
|
fun getOrderedProducts(): Set<Product> = orders.flatMap { order -> order.products }.toSet()
|
||||||
|
|
||||||
|
fun getTotalOrderPrice(): Double = orders.flatMap { ord -> ord.products }.sumOf { pr -> pr.getPriceDouble()}
|
||||||
|
}
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
package com.example.demo.models
|
package com.example.demo.models
|
||||||
|
|
||||||
data class Shop(val name: String, val customers: List<Customer>) {
|
data class Shop(val name: String, val customers: List<CustomerLocal>) {
|
||||||
fun checkAllCustomersAreFrom(city: City): Boolean = customers.count { cus -> cus.city == city } == customers.count()
|
fun checkAllCustomersAreFrom(city: City): Boolean = customers.count { cus -> cus.city == city } == customers.count()
|
||||||
|
|
||||||
fun countCustomersFrom(city: City): Int = customers.count { cus -> cus.city == city }
|
fun countCustomersFrom(city: City): Int = customers.count { cus -> cus.city == city }
|
||||||
|
|
||||||
fun getCitiesCustomersAreFrom(): Set<City> = customers.map { cus -> cus.city }.toSet()
|
fun getCitiesCustomersAreFrom(): Set<City> = customers.map { cus -> cus.city }.toSet()
|
||||||
|
|
||||||
fun findAnyCustomerFrom(city: City): Customer? = customers.firstOrNull { cus -> cus.city == city }
|
fun findAnyCustomerFrom(city: City): CustomerLocal? = customers.firstOrNull { cus -> cus.city == city }
|
||||||
|
|
||||||
fun getAllOrderedProducts(): Set<Product> = customers.flatMap { cus -> cus.getOrderedProducts() }.toSet()
|
fun getAllOrderedProducts(): Set<Product> = customers.flatMap { cus -> cus.getOrderedProducts() }.toSet()
|
||||||
|
|
||||||
fun getCustomersFrom(city: City): List<Customer> = customers.filter { cus -> cus.city == city }
|
fun getCustomersFrom(city: City): List<CustomerLocal> = customers.filter { cus -> cus.city == city }
|
||||||
|
|
||||||
fun getCustomersSortedByNumberOfOrders(): List<Customer> = customers.sortedBy { cus -> cus.orders.count() }
|
fun getCustomersSortedByNumberOfOrders(): List<CustomerLocal> = customers.sortedBy { cus -> cus.orders.count() }
|
||||||
|
|
||||||
fun getCustomerWithMaximumNumberOfOrders(): Customer? = customers.maxByOrNull { cus -> cus.orders.count() }
|
fun getCustomerWithMaximumNumberOfOrders(): CustomerLocal? = customers.maxByOrNull { cus -> cus.orders.count() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return customers who have more undelivered orders than delivered
|
* Return customers who have more undelivered orders than delivered
|
||||||
*/
|
*/
|
||||||
fun getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> = customers.partition(predicate = fun (cus): Boolean {
|
fun getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<CustomerLocal> = customers.partition(predicate = fun (cus): Boolean {
|
||||||
val (del, undel) = cus.orders.partition { ord -> ord.isDelivered }
|
val (del, undel) = cus.orders.partition { ord -> ord.isDelivered }
|
||||||
|
|
||||||
return del.count() < undel.count()
|
return del.count() < undel.count()
|
||||||
@@ -42,7 +42,7 @@ data class Shop(val name: String, val customers: List<Customer>) {
|
|||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun groupCustomersByCity(): Map<City, List<Customer>> = customers.groupBy { cus -> cus.city }
|
fun groupCustomersByCity(): Map<City, List<CustomerLocal>> = customers.groupBy { cus -> cus.city }
|
||||||
|
|
||||||
fun hasCustomerFrom(city: City): Boolean = customers.any { cus -> cus.city == city }
|
fun hasCustomerFrom(city: City): Boolean = customers.any { cus -> cus.city == city }
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.example.demo.providers
|
||||||
|
|
||||||
|
import com.example.demo.models.Customer
|
||||||
|
import org.springframework.data.repository.CrudRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface CustomerRepository: CrudRepository<Customer, Long> {
|
||||||
|
fun findByGuid(guid: UUID): Customer?
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class MockedShopProvider: ShopProvider {
|
|||||||
val productFour = makeProduct(id = 4, name = "four", price = 14.2)
|
val productFour = makeProduct(id = 4, name = "four", price = 14.2)
|
||||||
|
|
||||||
return Shop(name="shop", customers= listOf(
|
return Shop(name="shop", customers= listOf(
|
||||||
Customer(
|
CustomerLocal(
|
||||||
name = "Foo-1",
|
name = "Foo-1",
|
||||||
city = makeCity(id = 1, name = "Foo"),
|
city = makeCity(id = 1, name = "Foo"),
|
||||||
orders = listOf(
|
orders = listOf(
|
||||||
@@ -20,7 +20,7 @@ class MockedShopProvider: ShopProvider {
|
|||||||
Order(products = listOf(productThree), isDelivered = false),
|
Order(products = listOf(productThree), isDelivered = false),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Customer(
|
CustomerLocal(
|
||||||
name = "Foo-2",
|
name = "Foo-2",
|
||||||
city = makeCity(id = 2, name = "Bar"),
|
city = makeCity(id = 2, name = "Bar"),
|
||||||
orders = listOf(
|
orders = listOf(
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.example.demo.services.database.customer
|
||||||
|
|
||||||
|
import com.example.demo.models.Customer
|
||||||
|
import com.example.demo.services.database.exceptions.CityNotFoundException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
interface CustomerService {
|
||||||
|
fun findByGuid(guid: UUID): Customer?
|
||||||
|
|
||||||
|
@Throws(CityNotFoundException::class)
|
||||||
|
fun create(name: String, cityGuid: UUID?): Customer
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.example.demo.services.database.customer
|
||||||
|
|
||||||
|
import com.example.demo.models.Customer
|
||||||
|
import com.example.demo.providers.CityRepository
|
||||||
|
import com.example.demo.providers.CustomerRepository
|
||||||
|
import com.example.demo.services.database.exceptions.CityNotFoundException
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class CustomerServiceImpl(
|
||||||
|
private val customerRepository: CustomerRepository,
|
||||||
|
private val cityRepository: CityRepository
|
||||||
|
): CustomerService {
|
||||||
|
override fun findByGuid(guid: UUID): Customer? = customerRepository.findByGuid(guid)
|
||||||
|
|
||||||
|
override fun create(name: String, cityGuid: UUID?): Customer {
|
||||||
|
val cityId: Long? = cityGuid?.let {
|
||||||
|
cityRepository.findByGuid(it)?.id ?: throw CityNotFoundException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val customer = Customer(
|
||||||
|
id = null,
|
||||||
|
guid = UUID.randomUUID(),
|
||||||
|
name = name,
|
||||||
|
cityId = cityId,
|
||||||
|
createdAt = OffsetDateTime.now(),
|
||||||
|
updatedAt = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
return customerRepository.save(customer)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
create table customer (
|
||||||
|
id bigserial primary key,
|
||||||
|
guid uuid not null,
|
||||||
|
name varchar(255) not null,
|
||||||
|
city_id bigint,
|
||||||
|
created_at timestamptz not null,
|
||||||
|
updated_at timestamptz,
|
||||||
|
CONSTRAINT customer_city_foreign
|
||||||
|
FOREIGN KEY(city_id)
|
||||||
|
REFERENCES city(id)
|
||||||
|
ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
create unique index customer_guid_idx ON customer (guid);
|
||||||
@@ -28,7 +28,7 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
|
|||||||
val productFour = makeProduct(id = 4, name = "four", price = 14.2)
|
val productFour = makeProduct(id = 4, name = "four", price = 14.2)
|
||||||
|
|
||||||
val shopMock = Shop(name="shop", customers= listOf(
|
val shopMock = Shop(name="shop", customers= listOf(
|
||||||
Customer(
|
CustomerLocal(
|
||||||
name = "cus-one",
|
name = "cus-one",
|
||||||
city = makeCity(id = 1, name = "city-one"),
|
city = makeCity(id = 1, name = "city-one"),
|
||||||
orders = listOf(
|
orders = listOf(
|
||||||
@@ -37,7 +37,7 @@ class ShopControllerTest(@Autowired val mockMvc: MockMvc): BaseUnitTest() {
|
|||||||
Order(products = listOf(productThree), isDelivered = true),
|
Order(products = listOf(productThree), isDelivered = true),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Customer(
|
CustomerLocal(
|
||||||
name = "cus-two",
|
name = "cus-two",
|
||||||
city = makeCity(id = 2, name = "city-two"),
|
city = makeCity(id = 2, name = "city-two"),
|
||||||
orders = listOf(
|
orders = listOf(
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package com.example.demo.services.database.city
|
|||||||
import com.example.demo.BaseDbTest
|
import com.example.demo.BaseDbTest
|
||||||
import com.example.demo.models.City
|
import com.example.demo.models.City
|
||||||
import com.example.demo.providers.CityRepository
|
import com.example.demo.providers.CityRepository
|
||||||
import com.example.demo.services.database.exceptions.CityNotFoundException
|
|
||||||
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
import com.example.demo.services.database.exceptions.AlreadyDeletedException
|
||||||
|
import com.example.demo.services.database.exceptions.CityNotFoundException
|
||||||
import org.junit.jupiter.api.assertThrows
|
import org.junit.jupiter.api.assertThrows
|
||||||
import org.springframework.beans.factory.annotation.Autowired
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
import org.springframework.test.context.ContextConfiguration
|
import org.springframework.test.context.ContextConfiguration
|
||||||
@@ -25,7 +25,6 @@ class CityServiceImplDbTest: BaseDbTest() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
city = cityServiceImpl.create(name = name)
|
city = cityServiceImpl.create(name = name)
|
||||||
assertNotNull(city)
|
|
||||||
assertNotNull(city.id)
|
assertNotNull(city.id)
|
||||||
assertEquals(name, city.name)
|
assertEquals(name, city.name)
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ class CityServiceImplDbTest: BaseDbTest() {
|
|||||||
assertFalse(dbCity.isDeleted())
|
assertFalse(dbCity.isDeleted())
|
||||||
|
|
||||||
val deletedCity = cityServiceImpl.delete(city.guid)
|
val deletedCity = cityServiceImpl.delete(city.guid)
|
||||||
assertNotNull(deletedCity)
|
|
||||||
assertEquals(city.id, deletedCity.id)
|
assertEquals(city.id, deletedCity.id)
|
||||||
assertNotNull(deletedCity.deletedAt)
|
assertNotNull(deletedCity.deletedAt)
|
||||||
assertTrue(deletedCity.isDeleted())
|
assertTrue(deletedCity.isDeleted())
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.example.demo.services.database.customer
|
||||||
|
|
||||||
|
import com.example.demo.BaseDbTest
|
||||||
|
import com.example.demo.models.City
|
||||||
|
import com.example.demo.providers.CityRepository
|
||||||
|
import com.example.demo.providers.CustomerRepository
|
||||||
|
import com.example.demo.services.database.exceptions.CityNotFoundException
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired
|
||||||
|
import org.springframework.test.context.ContextConfiguration
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
@ContextConfiguration(classes = [CustomerRepository::class, CityRepository::class, CustomerServiceImpl::class])
|
||||||
|
class CustomerServiceImplDbTest: BaseDbTest() {
|
||||||
|
@Autowired
|
||||||
|
private lateinit var customerRepository: CustomerRepository
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var cityRepository: CityRepository
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private lateinit var customerServiceImpl: CustomerServiceImpl
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun createFind_success() {
|
||||||
|
val nameOne = "Some Dude-One"
|
||||||
|
val nameTwo = "Some Dude-Two"
|
||||||
|
val nameThree = "Some Dude-Three"
|
||||||
|
var city = City(
|
||||||
|
id = null,
|
||||||
|
guid = UUID.randomUUID(),
|
||||||
|
name = "some city name",
|
||||||
|
createdAt = OffsetDateTime.now(),
|
||||||
|
updatedAt = null,
|
||||||
|
deletedAt = null,
|
||||||
|
)
|
||||||
|
var customerIds = longArrayOf()
|
||||||
|
|
||||||
|
try {
|
||||||
|
city = cityRepository.save(city)
|
||||||
|
|
||||||
|
customerServiceImpl.create(nameOne, city.guid).let {
|
||||||
|
customerIds += it.id ?: fail("customerWithCity id is null")
|
||||||
|
assertEquals(city.id, it.cityId)
|
||||||
|
assertNotNull(it.createdAt)
|
||||||
|
assertNull(it.updatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
val customerWithNoCity = customerServiceImpl.create(nameTwo, null)
|
||||||
|
customerIds += customerWithNoCity.id ?: fail("customerWithNoCity id is null")
|
||||||
|
assertNull(customerWithNoCity.cityId)
|
||||||
|
assertNotNull(customerWithNoCity.createdAt)
|
||||||
|
assertNull(customerWithNoCity.updatedAt)
|
||||||
|
|
||||||
|
val existedCustomer = customerServiceImpl.findByGuid(customerWithNoCity.guid)
|
||||||
|
assertNotNull(existedCustomer)
|
||||||
|
assertEquals(customerWithNoCity.id, existedCustomer.id)
|
||||||
|
|
||||||
|
assertThrows<CityNotFoundException> {
|
||||||
|
customerServiceImpl.create(nameThree, UUID.randomUUID())
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
val cityId = city.id
|
||||||
|
if (cityId != null) {
|
||||||
|
cityRepository.deleteById(cityId)
|
||||||
|
}
|
||||||
|
|
||||||
|
customerIds.onEach { customerRepository.deleteById(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user