init service: base structure, some api-methods, migrations

This commit is contained in:
Denis Savosin
2024-09-25 10:08:52 +07:00
commit 6cfc4382d4
33 changed files with 787 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
package com.example.demo
import com.example.demo.provider.MockedShopProvider
import com.example.demo.provider.ShopProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class AppConfig {
@Bean
fun shopProvider(): ShopProvider{
return MockedShopProvider()
}
}

View File

@@ -0,0 +1,11 @@
package com.example.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}

View File

@@ -0,0 +1,18 @@
package com.example.demo.controllers
import com.example.demo.provider.html.renderProductTable
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class GreetingController {
@GetMapping("/greeting")
fun greet(): String {
return "Hello World!"
}
@GetMapping("/example/html")
fun exampleHtml(): String {
return renderProductTable()
}
}

View File

@@ -0,0 +1,39 @@
package com.example.demo.controllers
import com.example.demo.provider.ShopProvider
import jakarta.servlet.http.HttpServletResponse
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToJsonElement
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.bind.annotation.RestController
@RestController
class ShopController (
@field:Autowired private val shopProvider: ShopProvider,
) {
@GetMapping(value = ["/shop/common-info"], produces = ["application/json"] )
@ResponseBody
fun commonInfo(response: HttpServletResponse): String {
response.contentType = "application/json"
val shop = shopProvider.getRandomShop()
if (shop == null) {
response.status = HttpStatus.NOT_FOUND.value()
return "not found"
}
return Json.encodeToJsonElement(value = mapOf(
"customers" to mapOf(
"withMoreUndeliveredOrdersThanDelivered" to shop.getCustomersWithMoreUndeliveredOrdersThanDelivered().map { cus -> cus.name }
),
"products" to mapOf(
"orderedByAllCustomers" to shop.getSetOfProductsOrderedByEveryCustomer().map { pr -> pr.name },
)
)).toString()
}
}

View File

@@ -0,0 +1,3 @@
package com.example.demo.models
data class City(val name: String)

View File

@@ -0,0 +1,16 @@
package com.example.demo.models
data class Customer(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.price }
}

View File

@@ -0,0 +1,3 @@
package com.example.demo.models
data class Order(val products: List<Product>, val isDelivered: Boolean)

View File

@@ -0,0 +1,3 @@
package com.example.demo.models
data class Product(val name: String, val price: Double)

View File

@@ -0,0 +1,48 @@
package com.example.demo.models
data class Shop(val name: String, val customers: List<Customer>) {
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 getCitiesCustomersAreFrom(): Set<City> = customers.map { cus -> cus.city }.toSet()
fun findAnyCustomerFrom(city: City): Customer? = customers.firstOrNull { cus -> cus.city == city }
fun getAllOrderedProducts(): Set<Product> = customers.flatMap { cus -> cus.getOrderedProducts() }.toSet()
fun getCustomersFrom(city: City): List<Customer> = customers.filter { cus -> cus.city == city }
fun getCustomersSortedByNumberOfOrders(): List<Customer> = customers.sortedBy { cus -> cus.orders.count() }
fun getCustomerWithMaximumNumberOfOrders(): Customer? = customers.maxByOrNull { cus -> cus.orders.count() }
/**
* Return customers who have more undelivered orders than delivered
*/
fun getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> = customers.partition(predicate = fun (cus): Boolean {
val (del, undel) = cus.orders.partition { ord -> ord.isDelivered }
return del.count() < undel.count()
}).first.toSet()
fun getNumberOfTimesProductWasOrdered(product: Product): Int = customers
.flatMap { cus -> cus.orders.flatMap { ord -> ord.products } }
.count { pr -> pr.name == product.name }
/**
* Return the set of products that were ordered by every customer.
* Note: a customer may order the same product for several times.
*/
fun getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
val products = customers.flatMap { cus -> cus.orders.flatMap { ord -> ord.products } }.toSet()
return customers.fold(products) { orderedProducts, cus ->
orderedProducts.intersect(cus.orders.flatMap { ord -> ord.products }.toSet())
}.toSet()
}
fun groupCustomersByCity(): Map<City, List<Customer>> = customers.groupBy { cus -> cus.city }
fun hasCustomerFrom(city: City): Boolean = customers.any { cus -> cus.city == city }
}

View File

@@ -0,0 +1,28 @@
package com.example.demo.provider
import com.example.demo.models.*
class MockedShopProvider: ShopProvider {
override fun getRandomShop(): Shop? {
return Shop(name="shop", customers= listOf(
Customer(
name = "Foo-1",
city = City(name= "Bar"),
orders = listOf(
Order(products = listOf(Product(name = "one", price = 11.2)), isDelivered = false),
Order(products = listOf(Product(name = "two", price = 13.2)), isDelivered = false),
Order(products = listOf(Product(name = "three", price = 15.2)), isDelivered = true),
)
),
Customer(
name = "Foo-2",
city = City(name= "Bar"),
orders = listOf(
Order(products = listOf(Product(name = "one", price = 12.2)), isDelivered = false),
Order(products = listOf(Product(name = "two", price = 13.2)), isDelivered = true),
Order(products = listOf(Product(name = "four", price = 14.2)), isDelivered = true),
)
),
))
}
}

View File

@@ -0,0 +1,7 @@
package com.example.demo.provider
import com.example.demo.models.Shop
interface ShopProvider {
fun getRandomShop(): Shop?
}

View File

@@ -0,0 +1,5 @@
package com.example.demo.provider.html
class Attribute(val name : String, val value : String) {
override fun toString() = """$name="$value" """
}

View File

@@ -0,0 +1,3 @@
package com.example.demo.provider.html
class Center: Tag("center")

View File

@@ -0,0 +1,12 @@
package com.example.demo.provider.html
class Html: Tag("html")
fun html(init: Html.() -> Unit): Html {
val tag = Html()
tag.init()
return tag
}
fun Html.table(init : Table.() -> Unit) = doInit(Table(), init)
fun Html.center(init : Center.() -> Unit) = doInit(Center(), init)

View File

@@ -0,0 +1,38 @@
package com.example.demo.provider.html
fun getTitleColor() = "#b9c9fe"
fun getCellColor(index: Int, row: Int) = if ((index + row) %2 == 0) "#dce4ff" else "#eff2ff"
fun renderProductTable(): String {
return html {
table {
tr (color = getTitleColor()) {
td {
text("Product")
}
td {
text("Price")
}
td {
text("Popularity")
}
}
val products = getProducts()
for ((i, product) in products.withIndex()) {
tr {
td (color = getCellColor(0, i+1)) {
text(product.description)
}
td (color = getCellColor(1, i+1)) {
text(product.price)
}
td (color = getCellColor(2, i+1)) {
text(product.popularity)
}
}
}
}
}.toString()
}

View File

@@ -0,0 +1,11 @@
package com.example.demo.provider.html
data class Product(val description: String, val price: Double, val popularity: Int)
fun getProducts(): Set<Product> {
return setOf(
Product("one", 12.0, 12),
Product("two", 13.0, 20),
Product("three", 14.0, 50)
)
}

View File

@@ -0,0 +1,3 @@
package com.example.demo.provider.html
class TD: Tag("td")

View File

@@ -0,0 +1,7 @@
package com.example.demo.provider.html
class TR: Tag("tr")
fun TR.td(color: String? = null, align : String = "left", init : TD.() -> Unit) = doInit(TD(), init)
.set("align", align)
.set("bgcolor", color)

View File

@@ -0,0 +1,5 @@
package com.example.demo.provider.html
class Table: Tag("table")
fun Table.tr(color: String? = null, init : TR.() -> Unit) = doInit(TR(), init).set("bgcolor", color)

View File

@@ -0,0 +1,28 @@
package com.example.demo.provider.html
open class Tag(val name: String) {
val children: MutableList<Tag> = ArrayList()
val attributes: MutableList<Attribute> = ArrayList()
override fun toString(): String {
return "<$name" +
(if (attributes.isEmpty()) "" else attributes.joinToString(separator = "", prefix = " ")) + ">" +
(if (children.isEmpty()) "" else children.joinToString(separator = "")) +
"</$name>"
}
}
fun <T: Tag> T.set(name: String, value: String?): T {
if (value != null) {
attributes.add(Attribute(name, value))
}
return this
}
fun <T: Tag> Tag.doInit(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
fun Tag.text(s : Any?) = doInit(Text(s.toString()), {})

View File

@@ -0,0 +1,5 @@
package com.example.demo.provider.html
class Text(val text: String): Tag("b") {
override fun toString() = text
}

View File

@@ -0,0 +1,16 @@
---
spring:
application:
name: Demo
datasource:
url: jdbc:postgresql://localhost:5432/demo?currentSchema=demo
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
hikari:
schema: demo
flyway: #flyway automatically uses the datasource from the application to connect to the DB
enabled: true # enables flyway database migration
locations: classpath:db/migration/structure, classpath:db/migration/data # the location where flyway should look for migration scripts
validate-on-migrate: true
default-schema: demo

View File

@@ -0,0 +1,4 @@
insert into product (guid, name, description, price, created_at) values
(gen_random_uuid(), 'salt', 'simple salt', 1200, now()),
(gen_random_uuid(), 'pepper', 'black pepper', 2099, now()),
(gen_random_uuid(), 'sugar', 'sweet sugar', 3129, now());

View File

@@ -0,0 +1,9 @@
create table product (
id bigserial primary key,
guid uuid not null,
name varchar(255) not null,
description text,
price bigint not null,
created_at timestamptz not null,
updated_at timestamptz
)

View File

@@ -0,0 +1,13 @@
package com.example.demo
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class DemoApplicationTests {
@Test
fun contextLoads() {
}
}