Update Ktor to 2.0.2

Signed-off-by: Billy Brawner <me@wbrawner.com>
This commit is contained in:
William Brawner 2022-06-22 13:42:01 -05:00
parent 698051fab1
commit b170d632e4
14 changed files with 123 additions and 95 deletions

View file

@ -23,20 +23,20 @@ running, you can run the app from the command line with gradle:
Some parameters of Twigs can be configured via environment variables: Some parameters of Twigs can be configured via environment variables:
Environment Variable|Default Value|Note | Environment Variable | Default Value | Note |
:---:|:---:|:--- |:--------------------:|:-------------:|:--------------------------------------------------------|
`TWIGS_PORT`|`8080`|Port for web server to listen on | `TWIGS_PORT` | `8080` | Port for web server to listen on |
`TWIGS_DB_HOST`|`localhost`|PostgreSQL server host | `TWIGS_DB_HOST` | `localhost` | PostgreSQL server host |
`TWIGS_DB_PORT`|`5432`|PostgreSQL server port | `TWIGS_DB_PORT` | `5432` | PostgreSQL server port |
`TWIGS_DB_NAME`|`twigs`|PostgreSQL database name | `TWIGS_DB_NAME` | `twigs` | PostgreSQL database name |
`TWIGS_DB_USER`|`twigs`|PostgreSQL database user | `TWIGS_DB_USER` | `twigs` | PostgreSQL database user |
`TWIGS_DB_PASS`|`twigs`|PostgreSQL database password | `TWIGS_DB_PASS` | `twigs` | PostgreSQL database password |
`TWIGS_PW_SALT`||Salt to use for password, generated if empty or null | `TWIGS_PW_SALT` | | Salt to use for password, generated if empty or null |
`TWIGS_SMTP_FROM`||From email address for automated emails sent from Twigs | `TWIGS_SMTP_FROM` | | From email address for automated emails sent from Twigs |
`TWIGS_SMTP_HOST`||SMTP server host for sending emails | `TWIGS_SMTP_HOST` | | SMTP server host for sending emails |
`TWIGS_SMTP_PORT`||SMTP server port for sending emails | `TWIGS_SMTP_PORT` | | SMTP server port for sending emails |
`TWIGS_SMTP_USER`||SMTP server username for sending emails | `TWIGS_SMTP_USER` | | SMTP server username for sending emails |
`TWIGS_SMTP_PASS`||SMTP server password for sending emails | `TWIGS_SMTP_PASS` | | SMTP server password for sending emails |
## Building ## Building

View file

@ -5,10 +5,10 @@ import com.wbrawner.twigs.model.Permission
import com.wbrawner.twigs.model.Session import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.storage.BudgetRepository import com.wbrawner.twigs.storage.BudgetRepository
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.response.* import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.util.pipeline.* import io.ktor.util.pipeline.*
suspend inline fun PipelineContext<Unit, ApplicationCall>.requireBudgetWithPermission( suspend inline fun PipelineContext<Unit, ApplicationCall>.requireBudgetWithPermission(
@ -58,5 +58,5 @@ suspend inline fun PipelineContext<Unit, ApplicationCall>.errorResponse(
) { ) {
message?.let { message?.let {
call.respond(httpStatusCode, ErrorResponse(message)) call.respond(httpStatusCode, ErrorResponse(message))
}?: call.respond(httpStatusCode) } ?: call.respond(httpStatusCode)
} }

View file

@ -6,12 +6,12 @@ import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.model.UserPermission import com.wbrawner.twigs.model.UserPermission
import com.wbrawner.twigs.storage.BudgetRepository import com.wbrawner.twigs.storage.BudgetRepository
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.server.application.*
import io.ktor.response.* import io.ktor.server.auth.*
import io.ktor.routing.* import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.budgetRoutes( fun Application.budgetRoutes(
budgetRepository: BudgetRepository, budgetRepository: BudgetRepository,
@ -34,7 +34,12 @@ fun Application.budgetRoutes(
} }
get("/{id}") { get("/{id}") {
budgetWithPermission(budgetRepository, permissionRepository, call.parameters["id"]!!, Permission.READ) { budget -> budgetWithPermission(
budgetRepository,
permissionRepository,
call.parameters["id"]!!,
Permission.READ
) { budget ->
val users = permissionRepository.findAll(budgetIds = listOf(budget.id)) val users = permissionRepository.findAll(budgetIds = listOf(budget.id))
call.respond(BudgetResponse(budget, users)) call.respond(BudgetResponse(budget, users))
} }
@ -77,7 +82,12 @@ fun Application.budgetRoutes(
} }
put("/{id}") { put("/{id}") {
budgetWithPermission(budgetRepository, permissionRepository, call.parameters["id"]!!, Permission.MANAGE) { budget -> budgetWithPermission(
budgetRepository,
permissionRepository,
call.parameters["id"]!!,
Permission.MANAGE
) { budget ->
val request = call.receive<BudgetRequest>() val request = call.receive<BudgetRequest>()
val name = request.name ?: budget.name val name = request.name ?: budget.name
val description = request.description ?: budget.description val description = request.description ?: budget.description
@ -99,7 +109,12 @@ fun Application.budgetRoutes(
} }
delete("/{id}") { delete("/{id}") {
budgetWithPermission(budgetRepository, permissionRepository, budgetId = call.parameters["id"]!!, Permission.OWNER) { budget -> budgetWithPermission(
budgetRepository,
permissionRepository,
budgetId = call.parameters["id"]!!,
Permission.OWNER
) { budget ->
budgetRepository.delete(budget) budgetRepository.delete(budget)
call.respond(HttpStatusCode.NoContent) call.respond(HttpStatusCode.NoContent)
} }

View file

@ -5,12 +5,12 @@ import com.wbrawner.twigs.model.Permission
import com.wbrawner.twigs.model.Session import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.storage.CategoryRepository import com.wbrawner.twigs.storage.CategoryRepository
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.server.application.*
import io.ktor.response.* import io.ktor.server.auth.*
import io.ktor.routing.* import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.categoryRoutes( fun Application.categoryRoutes(
categoryRepository: CategoryRepository, categoryRepository: CategoryRepository,

View file

@ -5,12 +5,12 @@ import com.wbrawner.twigs.model.RecurringTransaction
import com.wbrawner.twigs.model.Session import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import com.wbrawner.twigs.storage.RecurringTransactionRepository import com.wbrawner.twigs.storage.RecurringTransactionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.server.application.*
import io.ktor.response.* import io.ktor.server.auth.*
import io.ktor.routing.* import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.* import io.ktor.util.pipeline.*
import java.time.Instant import java.time.Instant

View file

@ -5,12 +5,12 @@ import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.model.Transaction import com.wbrawner.twigs.model.Transaction
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import com.wbrawner.twigs.storage.TransactionRepository import com.wbrawner.twigs.storage.TransactionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.server.application.*
import io.ktor.response.* import io.ktor.server.auth.*
import io.ktor.routing.* import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.time.Instant import java.time.Instant
fun Application.transactionRoutes( fun Application.transactionRoutes(
@ -32,7 +32,7 @@ fun Application.transactionRoutes(
from = call.request.queryParameters["from"]?.let { Instant.parse(it) }, from = call.request.queryParameters["from"]?.let { Instant.parse(it) },
to = call.request.queryParameters["to"]?.let { Instant.parse(it) }, to = call.request.queryParameters["to"]?.let { Instant.parse(it) },
expense = call.request.queryParameters["expense"]?.toBoolean(), expense = call.request.queryParameters["expense"]?.toBoolean(),
).map { it.asResponse() }) ).map { it.asResponse() })
} }
get("/{id}") { get("/{id}") {

View file

@ -7,12 +7,12 @@ import com.wbrawner.twigs.storage.PasswordResetRepository
import com.wbrawner.twigs.storage.PermissionRepository import com.wbrawner.twigs.storage.PermissionRepository
import com.wbrawner.twigs.storage.SessionRepository import com.wbrawner.twigs.storage.SessionRepository
import com.wbrawner.twigs.storage.UserRepository import com.wbrawner.twigs.storage.UserRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.request.* import io.ktor.server.application.*
import io.ktor.response.* import io.ktor.server.auth.*
import io.ktor.routing.* import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.time.Instant import java.time.Instant
fun Application.userRoutes( fun Application.userRoutes(

View file

@ -22,9 +22,7 @@ dependencies {
implementation(project(":db")) implementation(project(":db"))
implementation(project(":web")) implementation(project(":web"))
implementation(libs.kotlin.reflect) implementation(libs.kotlin.reflect)
implementation(libs.ktor.server.core) implementation(libs.bundles.ktor.server)
implementation(libs.ktor.server.cio)
implementation(libs.ktor.server.sessions)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation(libs.logback) implementation(libs.logback)
implementation(libs.mail) implementation(libs.mail)

View file

@ -8,13 +8,15 @@ import com.wbrawner.twigs.storage.*
import com.wbrawner.twigs.web.webRoutes import com.wbrawner.twigs.web.webRoutes
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.features.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.response.* import io.ktor.serialization.kotlinx.json.*
import io.ktor.serialization.* import io.ktor.server.application.*
import io.ktor.sessions.* import io.ktor.server.auth.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
@ -80,14 +82,14 @@ fun Application.moduleWithDependencies(
call.respond(HttpStatusCode.Unauthorized) call.respond(HttpStatusCode.Unauthorized)
} }
validate { session -> validate { session ->
environment.log.info("Validating session") application.environment.log.info("Validating session")
val storedSession = sessionRepository.findAll(session.token) val storedSession = sessionRepository.findAll(session.token)
.firstOrNull() .firstOrNull()
if (storedSession == null) { if (storedSession == null) {
environment.log.info("Did not find session!") application.environment.log.info("Did not find session!")
return@validate null return@validate null
} else { } else {
environment.log.info("Found session!") application.environment.log.info("Found session!")
} }
return@validate if (twoWeeksFromNow.isAfter(storedSession.expiration)) { return@validate if (twoWeeksFromNow.isAfter(storedSession.expiration)) {
sessionRepository.save(storedSession.copy(expiration = twoWeeksFromNow)) sessionRepository.save(storedSession.copy(expiration = twoWeeksFromNow))
@ -101,7 +103,7 @@ fun Application.moduleWithDependencies(
header<Session>("Authorization") { header<Session>("Authorization") {
serializer = object : SessionSerializer<Session> { serializer = object : SessionSerializer<Session> {
override fun deserialize(text: String): Session { override fun deserialize(text: String): Session {
environment.log.info("Deserializing session!") this@moduleWithDependencies.environment.log.info("Deserializing session!")
return Session(token = text.substringAfter("Bearer ")) return Session(token = text.substringAfter("Bearer "))
} }
@ -122,28 +124,28 @@ fun Application.moduleWithDependencies(
}) })
} }
install(CORS) { install(CORS) {
host("twigs.wbrawner.com", listOf("http", "https")) // TODO: Make configurable allowHost("twigs.wbrawner.com", listOf("http", "https")) // TODO: Make configurable
method(HttpMethod.Options) allowMethod(HttpMethod.Options)
method(HttpMethod.Put) allowMethod(HttpMethod.Put)
method(HttpMethod.Delete) allowMethod(HttpMethod.Delete)
header(HttpHeaders.Authorization) allowHeader(HttpHeaders.Authorization)
header(HttpHeaders.Accept) allowHeader(HttpHeaders.Accept)
header(HttpHeaders.AcceptEncoding) allowHeader(HttpHeaders.AcceptEncoding)
header(HttpHeaders.AcceptLanguage) allowHeader(HttpHeaders.AcceptLanguage)
header(HttpHeaders.Connection) allowHeader(HttpHeaders.Connection)
header(HttpHeaders.ContentType) allowHeader(HttpHeaders.ContentType)
header(HttpHeaders.Host) allowHeader(HttpHeaders.Host)
header(HttpHeaders.Origin) allowHeader(HttpHeaders.Origin)
header(HttpHeaders.AccessControlRequestHeaders) allowHeader(HttpHeaders.AccessControlRequestHeaders)
header(HttpHeaders.AccessControlRequestMethod) allowHeader(HttpHeaders.AccessControlRequestMethod)
header("Sec-Fetch-Dest") allowHeader("Sec-Fetch-Dest")
header("Sec-Fetch-Mode") allowHeader("Sec-Fetch-Mode")
header("Sec-Fetch-Site") allowHeader("Sec-Fetch-Site")
header("sec-ch-ua") allowHeader("sec-ch-ua")
header("sec-ch-ua-mobile") allowHeader("sec-ch-ua-mobile")
header("sec-ch-ua-platform") allowHeader("sec-ch-ua-platform")
header(HttpHeaders.UserAgent) allowHeader(HttpHeaders.UserAgent)
header("DNT") allowHeader("DNT")
allowCredentials = true allowCredentials = true
} }
budgetRoutes(budgetRepository, permissionRepository) budgetRoutes(budgetRepository, permissionRepository)

View file

@ -5,7 +5,7 @@ plugins {
dependencies { dependencies {
implementation(kotlin("stdlib")) implementation(kotlin("stdlib"))
api(libs.ktor.auth) api(libs.ktor.server.auth)
api(libs.bcrypt) api(libs.bcrypt)
testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine) testRuntimeOnly(libs.junit.jupiter.engine)

View file

@ -3,7 +3,7 @@ package com.wbrawner.twigs.model
import com.wbrawner.twigs.Identifiable import com.wbrawner.twigs.Identifiable
import com.wbrawner.twigs.randomString import com.wbrawner.twigs.randomString
import com.wbrawner.twigs.twoWeeksFromNow import com.wbrawner.twigs.twoWeeksFromNow
import io.ktor.auth.* import io.ktor.server.auth.*
import java.time.Instant import java.time.Instant
data class Session( data class Session(

View file

@ -2,7 +2,7 @@ package com.wbrawner.twigs.model
import com.wbrawner.twigs.Identifiable import com.wbrawner.twigs.Identifiable
import com.wbrawner.twigs.randomString import com.wbrawner.twigs.randomString
import io.ktor.auth.* import io.ktor.server.auth.*
data class User( data class User(
override val id: String = randomString(), override val id: String = randomString(),

View file

@ -4,7 +4,7 @@ hikari = "5.0.1"
junit = "5.8.2" junit = "5.8.2"
kotlin = "1.6.21" kotlin = "1.6.21"
kotlinx-coroutines = "1.6.2" kotlinx-coroutines = "1.6.2"
ktor = "1.6.6" ktor = "2.0.2"
logback = "1.2.11" logback = "1.2.11"
mail = "1.6.2" mail = "1.6.2"
postgres = "42.3.4" postgres = "42.3.4"
@ -15,19 +15,32 @@ bcrypt = { module = "at.favre.lib:bcrypt", version.ref = "bcrypt" }
hikari = { module = "com.zaxxer:HikariCP", version.ref = "hikari" } hikari = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" } junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
ktor-auth = { module = "io.ktor:ktor-auth", version.ref = "ktor" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
ktor-serialization = { module = "io.ktor:ktor-serialization", version.ref = "ktor" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
ktor-serialization = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
ktor-server-auth = { module = "io.ktor:ktor-server-auth", version.ref = "ktor" }
ktor-server-call-logging = { module = "io.ktor:ktor-server-call-logging", version.ref = "ktor" }
ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" } ktor-server-cio = { module = "io.ktor:ktor-server-cio", version.ref = "ktor" }
ktor-server-content-negotiation = { module = "io.ktor:ktor-server-content-negotiation", version.ref = "ktor" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-cors = { module = "io.ktor:ktor-server-cors", version.ref = "ktor" }
ktor-server-sessions = { module = "io.ktor:ktor-server-sessions", version.ref = "ktor" } ktor-server-sessions = { module = "io.ktor:ktor-server-sessions", version.ref = "ktor" }
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
mail = { module = "com.sun.mail:javax.mail", version.ref = "mail" } mail = { module = "com.sun.mail:javax.mail", version.ref = "mail" }
postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" } postgres = { module = "org.postgresql:postgresql", version.ref = "postgres" }
[bundles]
ktor-server = [
"ktor-server-call-logging",
"ktor-server-cio",
"ktor-server-content-negotiation",
"ktor-server-core",
"ktor-server-cors",
"ktor-server-sessions"
]
[plugins] [plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

View file

@ -1,10 +1,10 @@
package com.wbrawner.twigs.web package com.wbrawner.twigs.web
import io.ktor.application.* import io.ktor.server.application.*
import io.ktor.http.content.* import io.ktor.server.http.content.*
import io.ktor.request.* import io.ktor.server.request.*
import io.ktor.response.* import io.ktor.server.response.*
import io.ktor.routing.* import io.ktor.server.routing.*
fun Application.webRoutes() { fun Application.webRoutes() {
routing { routing {