add customer table, dto, repository, service

This commit is contained in:
Denis Savosin
2024-10-14 10:38:04 +07:00
parent 25fb73ffa4
commit fecbee8b28
12 changed files with 205 additions and 34 deletions

View File

@@ -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,

View File

@@ -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?,
)

View 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()}
}

View File

@@ -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 }
} }

View File

@@ -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?
}

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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);

View File

@@ -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(

View File

@@ -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())

View File

@@ -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) }
}
}
}