Update Ktor to 2.0.2
Signed-off-by: Billy Brawner <me@wbrawner.com>
This commit is contained in:
parent
698051fab1
commit
b170d632e4
14 changed files with 123 additions and 95 deletions
28
README.md
28
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}") {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue