Update javalin to v7 (major) (#1920)
* Update javalin to v7 * Update Javalin usage to v7 and Jackson 3 * Import fix --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Syer10 <syer10@users.noreply.github.com>
This commit is contained in:
@@ -4,9 +4,9 @@ coroutines = "1.11.0"
|
|||||||
serialization = "1.11.0"
|
serialization = "1.11.0"
|
||||||
jvmTarget = "21"
|
jvmTarget = "21"
|
||||||
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
|
||||||
javalin = "6.7.0"
|
javalin = "7.2.0"
|
||||||
jte = "3.2.4"
|
jte = "3.2.4"
|
||||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
jackson = "3.1.2" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
exposed = "0.61.0"
|
exposed = "0.61.0"
|
||||||
dex2jar = "2.4.36"
|
dex2jar = "2.4.36"
|
||||||
polyglot = "25.0.3"
|
polyglot = "25.0.3"
|
||||||
@@ -51,10 +51,10 @@ okio = "com.squareup.okio:okio:3.17.0"
|
|||||||
# Javalin api
|
# Javalin api
|
||||||
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||||
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
||||||
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" }
|
javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
|
||||||
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||||
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||||
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
|
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.20"
|
||||||
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
||||||
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import io.javalin.websocket.WsMessageContext
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.manga.impl.update.Websocket
|
import suwayomi.tachidesk.manga.impl.update.Websocket
|
||||||
|
|
||||||
object WebView : Websocket<String>() {
|
object WebView : Websocket<String>() {
|
||||||
|
|||||||
+7
-3
@@ -13,10 +13,14 @@ import com.expediagroup.graphql.server.types.GraphQLRequest
|
|||||||
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
import com.expediagroup.graphql.server.types.GraphQLServerRequest
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.UploadedFile
|
import io.javalin.http.UploadedFile
|
||||||
|
import io.javalin.json.JavalinJackson
|
||||||
|
import io.javalin.json.fromJsonStream
|
||||||
import io.javalin.json.fromJsonString
|
import io.javalin.json.fromJsonString
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||||
|
val jsonMapper = JavalinJackson()
|
||||||
|
|
||||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||||
return try {
|
return try {
|
||||||
@@ -29,17 +33,17 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
|||||||
context.formParam("operations")
|
context.formParam("operations")
|
||||||
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
||||||
} else {
|
} else {
|
||||||
return context.bodyAsClass(GraphQLServerRequest::class.java)
|
return context.bodyInputStream().use { jsonMapper.fromJsonStream<GraphQLServerRequest>(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val request =
|
val request =
|
||||||
context.jsonMapper().fromJsonString<GraphQLServerRequest>(formParam)
|
jsonMapper.fromJsonString<GraphQLServerRequest>(formParam)
|
||||||
|
|
||||||
val map =
|
val map =
|
||||||
context
|
context
|
||||||
.formParam("map")
|
.formParam("map")
|
||||||
?.let {
|
?.let {
|
||||||
context.jsonMapper().fromJsonString<Map<String, List<String>>>(it)
|
jsonMapper.fromJsonString<Map<String, List<String>>>(it)
|
||||||
}.orEmpty()
|
}.orEmpty()
|
||||||
|
|
||||||
val mapItems =
|
val mapItems =
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.onCompletion
|
|||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.job
|
import kotlinx.coroutines.job
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
||||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import kotlinx.coroutines.Job
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||||
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.CopyOnWriteArrayList
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
|||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import io.javalin.apibuilder.ApiBuilder.after
|
import io.javalin.apibuilder.ApiBuilder.after
|
||||||
import io.javalin.apibuilder.ApiBuilder.path
|
import io.javalin.apibuilder.ApiBuilder.path
|
||||||
|
import io.javalin.config.RoutesConfig
|
||||||
import io.javalin.http.Context
|
import io.javalin.http.Context
|
||||||
import io.javalin.http.HandlerType
|
import io.javalin.http.HandlerType
|
||||||
import io.javalin.http.HttpStatus
|
import io.javalin.http.HttpStatus
|
||||||
import io.javalin.http.NotFoundResponse
|
import io.javalin.http.NotFoundResponse
|
||||||
import io.javalin.http.RedirectResponse
|
import io.javalin.http.RedirectResponse
|
||||||
import io.javalin.http.UnauthorizedResponse
|
import io.javalin.http.UnauthorizedResponse
|
||||||
|
import io.javalin.json.JavalinJackson3
|
||||||
import io.javalin.rendering.template.JavalinJte
|
import io.javalin.rendering.template.JavalinJte
|
||||||
import io.javalin.websocket.WsContext
|
import io.javalin.websocket.WsContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -47,6 +49,7 @@ import java.net.URLEncoder
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.text.get
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
object JavalinSetup {
|
object JavalinSetup {
|
||||||
@@ -58,10 +61,12 @@ object JavalinSetup {
|
|||||||
|
|
||||||
fun javalinSetup() {
|
fun javalinSetup() {
|
||||||
val app =
|
val app =
|
||||||
Javalin.create { config ->
|
Javalin.start { config ->
|
||||||
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
||||||
config.fileRenderer(JavalinJte(templateEngine))
|
config.fileRenderer(JavalinJte(templateEngine))
|
||||||
|
|
||||||
|
config.jsonMapper(JavalinJackson3())
|
||||||
|
|
||||||
WebInterfaceManager.setup(config)
|
WebInterfaceManager.setup(config)
|
||||||
|
|
||||||
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
||||||
@@ -104,7 +109,8 @@ object JavalinSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.router.apiBuilder {
|
config.routes.defineCore()
|
||||||
|
config.routes.apiBuilder {
|
||||||
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
||||||
path("v1/") {
|
path("v1/") {
|
||||||
GlobalAPI.defineEndpoints()
|
GlobalAPI.defineEndpoints()
|
||||||
@@ -117,17 +123,32 @@ object JavalinSetup {
|
|||||||
after { ctx ->
|
after { ctx ->
|
||||||
// If not matched, the request was for an invalid endpoint
|
// If not matched, the request was for an invalid endpoint
|
||||||
// Return a 404 instead of redirecting to the UI for usability
|
// Return a 404 instead of redirecting to the UI for usability
|
||||||
if (ctx.endpointHandlerPath() == "*") {
|
if (ctx.endpoints().lastHttpEndpoint()?.path == "*") {
|
||||||
throw NotFoundResponse()
|
throw NotFoundResponse()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.events.serverStarted {
|
||||||
|
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||||
|
Browser.openInBrowser()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||||
|
Runtime.getRuntime().addShutdownHook(
|
||||||
|
thread(start = false) {
|
||||||
|
app.stop()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoutesConfig.defineCore() {
|
||||||
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
||||||
|
|
||||||
app.get(loginPath) { ctx ->
|
get(loginPath) { ctx ->
|
||||||
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
||||||
ctx.header("content-type", "text/html")
|
ctx.header("content-type", "text/html")
|
||||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||||
@@ -141,7 +162,7 @@ object JavalinSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.post(loginPath) { ctx ->
|
post(loginPath) { ctx ->
|
||||||
val username = ctx.formParam("user")
|
val username = ctx.formParam("user")
|
||||||
val password = ctx.formParam("pass")
|
val password = ctx.formParam("pass")
|
||||||
val isValid =
|
val isValid =
|
||||||
@@ -174,7 +195,7 @@ object JavalinSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.beforeMatched { ctx ->
|
beforeMatched { ctx ->
|
||||||
val isWebManifest =
|
val isWebManifest =
|
||||||
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
||||||
val isPageIcon =
|
val isPageIcon =
|
||||||
@@ -219,60 +240,43 @@ object JavalinSetup {
|
|||||||
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
||||||
}
|
}
|
||||||
|
|
||||||
app.events { event ->
|
wsBefore {
|
||||||
event.serverStarted {
|
|
||||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
|
||||||
Browser.openInBrowser()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.wsBefore {
|
|
||||||
it.onConnect { ctx ->
|
it.onConnect { ctx ->
|
||||||
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
exception(NullPointerException::class.java) { e, ctx ->
|
||||||
Runtime.getRuntime().addShutdownHook(
|
|
||||||
thread(start = false) {
|
|
||||||
app.stop()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
app.exception(NullPointerException::class.java) { e, ctx ->
|
|
||||||
logger.error(e) { "NullPointerException while handling the request" }
|
logger.error(e) { "NullPointerException while handling the request" }
|
||||||
ctx.status(404)
|
ctx.status(404)
|
||||||
}
|
}
|
||||||
app.exception(NoSuchElementException::class.java) { e, ctx ->
|
exception(NoSuchElementException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "NoSuchElementException while handling the request" }
|
logger.error(e) { "NoSuchElementException while handling the request" }
|
||||||
ctx.status(404)
|
ctx.status(404)
|
||||||
}
|
}
|
||||||
app.exception(IOException::class.java) { e, ctx ->
|
exception(IOException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "IOException while handling the request" }
|
logger.error(e) { "IOException while handling the request" }
|
||||||
ctx.status(500)
|
ctx.status(500)
|
||||||
ctx.result(e.message ?: "Internal Server Error")
|
ctx.result(e.message ?: "Internal Server Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(IllegalArgumentException::class.java) { e, ctx ->
|
exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "IllegalArgumentException while handling the request" }
|
logger.error(e) { "IllegalArgumentException while handling the request" }
|
||||||
ctx.status(400)
|
ctx.status(400)
|
||||||
ctx.result(e.message ?: "Bad Request")
|
ctx.result(e.message ?: "Bad Request")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(UnauthorizedException::class.java) { e, ctx ->
|
exception(UnauthorizedException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "UnauthorizedException while handling the request" }
|
logger.error(e) { "UnauthorizedException while handling the request" }
|
||||||
ctx.status(HttpStatus.UNAUTHORIZED)
|
ctx.status(HttpStatus.UNAUTHORIZED)
|
||||||
ctx.result(e.message ?: "Unauthorized")
|
ctx.result(e.message ?: "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exception(ForbiddenException::class.java) { e, ctx ->
|
exception(ForbiddenException::class.java) { e, ctx ->
|
||||||
logger.error(e) { "ForbiddenException while handling the request" }
|
logger.error(e) { "ForbiddenException while handling the request" }
|
||||||
ctx.status(HttpStatus.FORBIDDEN)
|
ctx.status(HttpStatus.FORBIDDEN)
|
||||||
ctx.result(e.message ?: "Forbidden")
|
ctx.result(e.message ?: "Forbidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
app.start()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private fun getOpenApiOptions(): OpenApiOptions {
|
// private fun getOpenApiOptions(): OpenApiOptions {
|
||||||
|
|||||||
@@ -71,15 +71,14 @@ fun <T> getParam(
|
|||||||
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
||||||
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
||||||
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
||||||
else -> throw IllegalStateException("Invalid param")
|
|
||||||
}.let {
|
}.let {
|
||||||
if (param.nullable) {
|
if (param.nullable) {
|
||||||
it.allowNullable().get() ?: param.defaultValue
|
it.getOrNull() ?: param.defaultValue
|
||||||
} else {
|
} else {
|
||||||
if (param.defaultValue != null) {
|
if (param.defaultValue != null) {
|
||||||
it.getOrDefault(param.defaultValue!!)
|
it.getOrDefault(param.defaultValue!!)
|
||||||
} else {
|
} else {
|
||||||
it.get()
|
it.required().get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import io.github.oshai.kotlinlogging.KLogger
|
|||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import io.github.reactivecircus.cache4k.Cache
|
import io.github.reactivecircus.cache4k.Cache
|
||||||
import io.javalin.config.JavalinConfig
|
import io.javalin.config.JavalinConfig
|
||||||
|
import io.javalin.http.staticfiles.AliasCheck
|
||||||
import io.javalin.http.staticfiles.Location
|
import io.javalin.http.staticfiles.Location
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
@@ -39,7 +40,6 @@ import kotlinx.serialization.json.jsonArray
|
|||||||
import kotlinx.serialization.json.jsonObject
|
import kotlinx.serialization.json.jsonObject
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import net.lingala.zip4j.ZipFile
|
import net.lingala.zip4j.ZipFile
|
||||||
import org.eclipse.jetty.server.handler.ContextHandler
|
|
||||||
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState
|
import suwayomi.tachidesk.graphql.types.UpdateState
|
||||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||||
@@ -180,7 +180,7 @@ object WebInterfaceManager {
|
|||||||
// Use canonical path to avoid Jetty alias issues
|
// Use canonical path to avoid Jetty alias issues
|
||||||
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
||||||
staticFiles.location = Location.EXTERNAL
|
staticFiles.location = Location.EXTERNAL
|
||||||
staticFiles.aliasCheck = ContextHandler.ApproveAliases()
|
staticFiles.aliasCheck = AliasCheck { _, _ -> true }
|
||||||
}
|
}
|
||||||
|
|
||||||
serveWebUI = {
|
serveWebUI = {
|
||||||
|
|||||||
Reference in New Issue
Block a user