-
Notifications
You must be signed in to change notification settings - Fork 0
/
MagicalTransfersApplication.kt
121 lines (112 loc) · 4.6 KB
/
MagicalTransfersApplication.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package com.krzykrucz.magicaltransfers
import org.springframework.boot.autoconfigure.data.mongo.MongoCoroutineDataAutoConfiguration
import org.springframework.boot.autoconfigure.data.mongo.MongoCoroutineRepositoriesAutoConfiguration
import org.springframework.core.io.ClassPathResource
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.CoroutineMongoTemplate
import org.springframework.data.mongodb.repository.CoroutineMongoRepository
import org.springframework.data.mongodb.repository.support.CoroutineMongoRepositoryFactory
import org.springframework.fu.kofu.application
import org.springframework.fu.kofu.mongo.reactiveMongodb
import org.springframework.fu.kofu.webflux.webFlux
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.EntityResponse
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.awaitBody
import org.springframework.web.reactive.function.server.bodyValueAndAwait
import org.springframework.web.reactive.function.server.buildAndAwait
import java.math.BigDecimal
import java.util.UUID
val app = application {
beans {
bean {
CoroutineMongoTemplate(ref())
.let(::CoroutineMongoRepositoryFactory)
.getRepository(AccountRepository::class.java)
}
bean<MongoCoroutineDataAutoConfiguration>()
bean<MongoCoroutineRepositoriesAutoConfiguration>()
/* FIXME there will be a separate security config in kofu.application when Spring Security DSL gets released
bean {
val http = ref<ServerHttpSecurity>()
http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
httpBasic { }
}
}
bean {
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
.let { MapReactiveUserDetailsService(it) }
}
*/
}
webFlux {
coRouter {
val accountRepository = ref<AccountRepository>()
POST("/credit") { request ->
val (accountNumber, money) = request.awaitBody<CreditAccountRequest>()
val account = accountRepository.findById(accountNumber)
?: throw AccountNotFoundException
val creditedAccount = account.credit(money)
val savedAccount = accountRepository.save(creditedAccount)
ServerResponse.ok().bodyValueAndAwait(savedAccount)
}
POST("/create/{accountNumber}") { request ->
val accountNumber = request.pathVariable("accountNumber")
Account(accountNumber, BigDecimal.ZERO)
.let { accountRepository.save(it) }
.let { ServerResponse.ok().bodyValueAndAwait(it) }
}
filter { request, handler ->
val trace = request.headers().firstHeader("Trace-Id") ?: "${UUID.randomUUID()}"
handler(request)
.let { response ->
val responseBuilder = ServerResponse.from(response)
.header("Trace-Id", trace)
if (response is EntityResponse<*>) responseBuilder.bodyValueAndAwait(response.entity())
else responseBuilder.buildAndAwait()
}
}
onError<Throwable> { error, _ ->
val status = when (error) {
is AccountNotFoundException -> HttpStatus.NOT_FOUND
else -> HttpStatus.INTERNAL_SERVER_ERROR
}
ServerResponse.status(status)
.contentType(MediaType.TEXT_PLAIN)
.bodyValueAndAwait(error.localizedMessage)
}
resources("/**", ClassPathResource("/htmls/"))
}
codecs {
string()
jackson()
resource()
}
}
reactiveMongodb {
embedded()
}
}
fun main(args: Array<String>) {
app.run(args)
}
data class Account(
@Id val number: String,
val balance: BigDecimal
) {
fun credit(money: BigDecimal) = copy(balance = balance + money)
}
interface AccountRepository : CoroutineMongoRepository<Account, String>
object AccountNotFoundException : RuntimeException("Account not found")
data class CreditAccountRequest(
val accountNumber: String,
val money: BigDecimal
)