Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
/*.iml
/kotlin-js-store/
/.kotlin/
/docs/
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ kotlin {
dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.html)
implementation(libs.ktor.openapi.schema)
}
}

Expand Down
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,20 @@ ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref =
ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" }
ktor-server-cors = { group = "io.ktor", name = "ktor-server-cors", version.ref = "ktor" }
ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
ktor-server-caching-headers = { group = "io.ktor", name = "ktor-server-caching-headers", version.ref = "ktor" }
ktor-server-compression = { group = "io.ktor", name = "ktor-server-compression", version.ref = "ktor" }
ktor-server-openapi = { group = "io.ktor", name = "ktor-server-openapi", version.ref = "ktor" }
ktor-server-routing-openapi = { group = "io.ktor", name = "ktor-server-routing-openapi", version.ref = "ktor" }
ktor-server-swagger = { group = "io.ktor", name = "ktor-server-swagger", version.ref = "ktor" }
ktor-network = { group = "io.ktor", name = "ktor-network", version.ref = "ktor" }
ktor-openapi-schema = { group = "io.ktor", name = "ktor-openapi-schema", version.ref = "ktor" }
ktor-redoc = { group = "io.github.smiley4", name = "ktor-redoc", version = "5.4.0" }
logback-classic = { group = "ch.qos.logback", name = "logback-classic", version = "1.5.28" }

zKtlint = { group = "com.pinterest.ktlint", name = "ktlint-cli", version.ref = "ktlint"}

[bundles]
ktor-server = [ "ktor-server-core", "ktor-server-netty", "ktor-server-websockets", "ktor-server-caching-headers", "ktor-server-compression" ]
ktor-server = [ "ktor-server-core", "ktor-server-netty", "ktor-server-cors", "ktor-server-websockets", "ktor-server-caching-headers", "ktor-server-compression", "ktor-server-openapi", "ktor-server-routing-openapi", "ktor-server-swagger", "ktor-redoc" ]
ktor-client = [ "ktor-client-core", "ktor-client-okhttp" ]
11 changes: 11 additions & 0 deletions src/commonMain/kotlin/net/kyori/adventure/webui/BuildInfo.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package net.kyori.adventure.webui

import io.ktor.openapi.JsonSchema
import kotlinx.serialization.Serializable

@JsonSchema.Title("BuildInfo")
@JsonSchema.Description("Information about the build of the server")
@Serializable
public data class BuildInfo(
@JsonSchema.Description("The time the server started")
@JsonSchema.Example("\"2021-08-22T19:20:00Z\"")
public val startedAt: String,
@JsonSchema.Description("The version of Adventure used by the server")
@JsonSchema.Example("4.10.0-SNAPSHOT")
public val version: String,
@JsonSchema.Description("The commit hash of code in use by the server")
@JsonSchema.Example("9f43339123b0ad37cfe210b6562e39b9a3ccf7c7")
public val commit: String,
@JsonSchema.Description("The URL of the Bytebin instance used by the server")
@JsonSchema.Example("\"https://bytebin.lucko.me\"")
public val bytebinInstance: String
)
50 changes: 49 additions & 1 deletion src/jvmMain/kotlin/net/kyori/adventure/webui/jvm/Application.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
package net.kyori.adventure.webui.jvm

import io.github.smiley4.ktorredoc.redoc
import io.ktor.http.CacheControl
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.http.content.CachingOptions
import io.ktor.openapi.OpenApiDoc
import io.ktor.openapi.OpenApiInfo
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.application.log
import io.ktor.server.plugins.cachingheaders.CachingHeaders
import io.ktor.server.plugins.compression.Compression
import io.ktor.server.plugins.compression.deflate
import io.ktor.server.plugins.compression.gzip
import io.ktor.server.plugins.cors.routing.CORS
import io.ktor.server.plugins.openapi.openAPI
import io.ktor.server.plugins.swagger.swaggerUI
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.openapi.OpenApiDocSource
import io.ktor.server.routing.openapi.OperationDescribeAttributeKey
import io.ktor.server.routing.openapi.plus
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import io.ktor.server.routing.routingRoot
import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.pingPeriod
import io.ktor.server.websocket.timeout
import io.ktor.websocket.WebSocketDeflateExtension
import kotlin.time.Duration
import kotlinx.serialization.json.Json
import kotlin.time.Duration.Companion.seconds

public fun Application.main() {
Expand Down Expand Up @@ -43,11 +58,44 @@ public fun Application.main() {
extensions { install(WebSocketDeflateExtension) }
}

install(CORS) {
anyHost()
allowHeader(HttpHeaders.ContentType)
}

routing {
// enable trace routing if in dev mode
if (developmentMode) {
trace { route -> this@main.log.debug(route.buildText()) }
}

// api docs
val oaInfo = OpenApiInfo(
"Adventure web API",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpicking, but is this the proper name? we call it adventure-webui and minimessage viewer elsewhere.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not that I would have a better name, adventure-webui api sounds dum too, lol

"Adventure ${getConfigString("miniMessageVersion")}",
description = "OpenAPI documentation for the Adventure web API.",
contact = OpenApiInfo.Contact("PaperMC Discord", "https://discord.gg/papermc", ""),
license = OpenApiInfo.License("The MIT License", "https://github.com/PaperMC/adventure-webui/blob/main/license.txt", "MIT"),
)
val oaSource = OpenApiDocSource.Routing {
routingRoot.descendants().filterNot { it.attributes.getOrNull(OperationDescribeAttributeKey).isNullOrEmpty() }
}
get("/api/docs.json") {
call.respondText(
Json.encodeToString(OpenApiDoc(info = oaInfo) + oaSource.routes(this@main)),
ContentType.Application.Json,
)
}
route("/api/redoc") {
redoc("/api/docs.json") {
pageTitle = "Adventure web API docs"
}
}
swaggerUI("/api/swagger") {
info = oaInfo
source = oaSource
faviconLocation = "/favicon-32x32.png"
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
@file:OptIn(ExperimentalKtorApi::class)

package net.kyori.adventure.webui.jvm.minimessage

import io.ktor.http.HttpStatusCode
import io.ktor.openapi.jsonSchema
import io.ktor.server.application.Application
import io.ktor.server.http.content.defaultResource
import io.ktor.server.http.content.resource
import io.ktor.server.http.content.resources
import io.ktor.server.http.content.static
import io.ktor.server.http.content.staticFiles
import io.ktor.server.http.content.staticResources
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
import io.ktor.server.routing.openapi.describe
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import io.ktor.server.routing.routing
import io.ktor.server.websocket.webSocket
import io.ktor.utils.io.ExperimentalKtorApi
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.serialization.encodeToString
import net.kyori.adventure.text.minimessage.MiniMessage
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver
Expand All @@ -41,6 +40,7 @@ import net.kyori.adventure.webui.jvm.minimessage.hook.FONT_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.HOVER_EVENT_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.HookManager
import net.kyori.adventure.webui.jvm.minimessage.hook.INSERTION_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.SHADOW_COLOR_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.TEXT_COLOR_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.TEXT_DECORATION_RENDER_HOOK
import net.kyori.adventure.webui.jvm.minimessage.hook.TEXT_RENDER_HOOK
Expand All @@ -55,8 +55,6 @@ import net.kyori.adventure.webui.websocket.ParseResult
import net.kyori.adventure.webui.websocket.Placeholders
import net.kyori.adventure.webui.websocket.Response
import java.time.Instant
import net.kyori.adventure.webui.jvm.minimessage.hook.SHADOW_COLOR_RENDER_HOOK
import java.io.File

private val startedAt = Instant.now()

Expand Down Expand Up @@ -220,6 +218,16 @@ public fun Application.miniMessage() {
bytebinInstance = BytebinStorage.BYTEBIN_INSTANCE,
)
call.respondText(Serializers.json.encodeToString(info))
}.describe {
operationId = "getBuildInfo"
summary = "Get build information"
description = "Returns information about the build of this application."
responses {
HttpStatusCode.OK {
description = "The build information"
schema = jsonSchema<BuildInfo>()
}
}
}

route(URL_EDITOR) { installEditor() }
Expand Down