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:
Environment Variable|Default Value|Note
:---:|:---:|:---
`TWIGS_PORT`|`8080`|Port for web server to listen on
`TWIGS_DB_HOST`|`localhost`|PostgreSQL server host
`TWIGS_DB_PORT`|`5432`|PostgreSQL server port
`TWIGS_DB_NAME`|`twigs`|PostgreSQL database name
`TWIGS_DB_USER`|`twigs`|PostgreSQL database user
`TWIGS_DB_PASS`|`twigs`|PostgreSQL database password
`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_HOST`||SMTP server host for sending emails
`TWIGS_SMTP_PORT`||SMTP server port for sending emails
`TWIGS_SMTP_USER`||SMTP server username for sending emails
`TWIGS_SMTP_PASS`||SMTP server password for sending emails
| Environment Variable | Default Value | Note |
|:--------------------:|:-------------:|:--------------------------------------------------------|
| `TWIGS_PORT` | `8080` | Port for web server to listen on |
| `TWIGS_DB_HOST` | `localhost` | PostgreSQL server host |
| `TWIGS_DB_PORT` | `5432` | PostgreSQL server port |
| `TWIGS_DB_NAME` | `twigs` | PostgreSQL database name |
| `TWIGS_DB_USER` | `twigs` | PostgreSQL database user |
| `TWIGS_DB_PASS` | `twigs` | PostgreSQL database password |
| `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_HOST` | | SMTP server host for sending emails |
| `TWIGS_SMTP_PORT` | | SMTP server port for sending emails |
| `TWIGS_SMTP_USER` | | SMTP server username for sending emails |
| `TWIGS_SMTP_PASS` | | SMTP server password for sending emails |
## Building

View file

@ -5,10 +5,10 @@ import com.wbrawner.twigs.model.Permission
import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.storage.BudgetRepository
import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
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.*
suspend inline fun PipelineContext<Unit, ApplicationCall>.requireBudgetWithPermission(
@ -58,5 +58,5 @@ suspend inline fun PipelineContext<Unit, ApplicationCall>.errorResponse(
) {
message?.let {
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.storage.BudgetRepository
import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.budgetRoutes(
budgetRepository: BudgetRepository,
@ -34,7 +34,12 @@ fun Application.budgetRoutes(
}
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))
call.respond(BudgetResponse(budget, users))
}
@ -77,7 +82,12 @@ fun Application.budgetRoutes(
}
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 name = request.name ?: budget.name
val description = request.description ?: budget.description
@ -99,7 +109,12 @@ fun Application.budgetRoutes(
}
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)
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.storage.CategoryRepository
import com.wbrawner.twigs.storage.PermissionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.categoryRoutes(
categoryRepository: CategoryRepository,

View file

@ -5,12 +5,12 @@ import com.wbrawner.twigs.model.RecurringTransaction
import com.wbrawner.twigs.model.Session
import com.wbrawner.twigs.storage.PermissionRepository
import com.wbrawner.twigs.storage.RecurringTransactionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.util.pipeline.*
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.storage.PermissionRepository
import com.wbrawner.twigs.storage.TransactionRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.time.Instant
fun Application.transactionRoutes(
@ -32,7 +32,7 @@ fun Application.transactionRoutes(
from = call.request.queryParameters["from"]?.let { Instant.parse(it) },
to = call.request.queryParameters["to"]?.let { Instant.parse(it) },
expense = call.request.queryParameters["expense"]?.toBoolean(),
).map { it.asResponse() })
).map { it.asResponse() })
}
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.SessionRepository
import com.wbrawner.twigs.storage.UserRepository
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.time.Instant
fun Application.userRoutes(

View file

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

View file

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

View file

@ -5,7 +5,7 @@ plugins {
dependencies {
implementation(kotlin("stdlib"))
api(libs.ktor.auth)
api(libs.ktor.server.auth)
api(libs.bcrypt)
testImplementation(libs.junit.jupiter.api)
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.randomString
import com.wbrawner.twigs.twoWeeksFromNow
import io.ktor.auth.*
import io.ktor.server.auth.*
import java.time.Instant
data class Session(

View file

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

View file

@ -4,7 +4,7 @@ hikari = "5.0.1"
junit = "5.8.2"
kotlin = "1.6.21"
kotlinx-coroutines = "1.6.2"
ktor = "1.6.6"
ktor = "2.0.2"
logback = "1.2.11"
mail = "1.6.2"
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" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit" }
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-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
ktor-auth = { module = "io.ktor:ktor-auth", version.ref = "ktor" }
ktor-serialization = { module = "io.ktor:ktor-serialization", version.ref = "ktor" }
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" }
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-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-cors = { module = "io.ktor:ktor-server-cors", 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" }
mail = { module = "com.sun.mail:javax.mail", version.ref = "mail" }
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]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", 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
import io.ktor.application.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.application.*
import io.ktor.server.http.content.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.webRoutes() {
routing {