diff --git a/build.gradle.kts b/build.gradle.kts index d8dc7f9..7c412b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,6 +19,10 @@ dependencies { implementation(Libs.javalin) implementation(Libs.slf4j_simple) implementation(Libs.jackson_databind) + implementation(Libs.jackson_module_kotlin) + implementation(Libs.swagger_core) + implementation(Libs.kotlin_openapi3_dsl) + implementation(Libs.swagger_ui) testImplementation(Libs.junit_jupiter) } diff --git a/buildSrc/src/main/kotlin/Libs.kt b/buildSrc/src/main/kotlin/Libs.kt index 1001201..fa5811b 100644 --- a/buildSrc/src/main/kotlin/Libs.kt +++ b/buildSrc/src/main/kotlin/Libs.kt @@ -7,12 +7,24 @@ import kotlin.String * `$ ./gradlew buildSrcVersions` */ object Libs { + /** + * https://github.com/derveloper/kotlin-openapi3-dsl + */ + const val kotlin_openapi3_dsl: String = "cc.vileda:kotlin-openapi3-dsl:" + + Versions.kotlin_openapi3_dsl + /** * http://github.com/FasterXML/jackson */ const val jackson_databind: String = "com.fasterxml.jackson.core:jackson-databind:" + Versions.jackson_databind + /** + * https://github.com/FasterXML/jackson-module-kotlin + */ + const val jackson_module_kotlin: String = "com.fasterxml.jackson.module:jackson-module-kotlin:" + + Versions.jackson_module_kotlin + const val com_github_johnrengelman_shadow_gradle_plugin: String = "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:" + Versions.com_github_johnrengelman_shadow_gradle_plugin @@ -26,6 +38,11 @@ object Libs { */ const val javalin: String = "io.javalin:javalin:" + Versions.javalin + /** + * https://github.com/swagger-api/swagger-core + */ + const val swagger_core: String = "io.swagger.core.v3:swagger-core:" + Versions.swagger_core + const val org_jetbrains_kotlin_jvm_gradle_plugin: String = "org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:" + Versions.org_jetbrains_kotlin_jvm_gradle_plugin @@ -51,4 +68,9 @@ object Libs { * http://www.slf4j.org */ const val slf4j_simple: String = "org.slf4j:slf4j-simple:" + Versions.slf4j_simple + + /** + * http://webjars.org + */ + const val swagger_ui: String = "org.webjars:swagger-ui:" + Versions.swagger_ui } diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 4a5fa73..1cbe45b 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -12,14 +12,20 @@ import org.gradle.plugin.use.PluginDependencySpec * YOU are responsible for updating manually the dependency version. */ object Versions { + const val kotlin_openapi3_dsl: String = "0.20.2" + const val jackson_databind: String = "2.10.0.pr1" + const val jackson_module_kotlin: String = "2.10.0.pr1" + const val com_github_johnrengelman_shadow_gradle_plugin: String = "5.1.0" const val de_fayard_buildsrcversions_gradle_plugin: String = "0.4.2" const val javalin: String = "3.4.1" + const val swagger_core: String = "2.0.9" + const val org_jetbrains_kotlin_jvm_gradle_plugin: String = "1.3.50" const val org_jetbrains_kotlin: String = "1.3.50" @@ -28,6 +34,8 @@ object Versions { const val slf4j_simple: String = "1.7.28" + const val swagger_ui: String = "3.23.5" + /** * * See issue 19: How to update Gradle itself? diff --git a/src/main/kotlin/com/anthonycicchetti/slackbot/Main.kt b/src/main/kotlin/com/anthonycicchetti/slackbot/Main.kt index 22264ad..0ac1c46 100644 --- a/src/main/kotlin/com/anthonycicchetti/slackbot/Main.kt +++ b/src/main/kotlin/com/anthonycicchetti/slackbot/Main.kt @@ -5,30 +5,68 @@ import com.anthonycicchetti.slackbot.utility.RespObj import com.anthonycicchetti.slackbot.utility.TextResponse import io.javalin.http.Context import io.javalin.Javalin +import io.javalin.plugin.openapi.OpenApiOptions +import io.javalin.plugin.openapi.OpenApiPlugin +import io.javalin.plugin.openapi.dsl.document +import io.javalin.plugin.openapi.dsl.documented +import io.javalin.plugin.openapi.ui.SwaggerOptions +import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.info.Info +import io.swagger.v3.oas.models.servers.Server import org.slf4j.LoggerFactory +import kotlin.math.roundToInt fun main() { val logger = LoggerFactory.getLogger("main") - val app = Javalin.create().start(7000) + val app = Javalin.create { config -> + config.registerPlugin(OpenApiPlugin(createOpenApiOptions())) + config.enableCorsForAllOrigins() + config.requestLogger {ctx, ms -> + logger.info("Took ${ms.roundToInt()} to process ${ctx.req.requestURL}") + } + }.start(7000) with(app) { - get("/") { ctx -> ctx.result("Hello World") } + options("/*", documented(document().ignore()) {}) + get("/", documented(document().ignore()) { ctx -> ctx.result("Hello World") }) - post("/slack/receive") { ctx -> + post("/slack/receive", documented(document().operation { + it.description = "NOT DOCUMENTED. Required for slack usage" + it.summary = "NOT DOCUMENTED. Required for slack usage" + }.ignore()) { ctx -> + logger.info("Received request from ${ctx.req.requestURL}") handleSlackEvent(ctx) - } + }) - post("/api/v1/spongebob") { ctx -> + post("/api/v1/spongebob", documented(document().operation { + it.description = "Responds with the ${"spongemocked".toSpongemock()} version of the string posted" + it.summary = "POST for ${"spongemocking".toSpongemock()}" + }.body().json("200")) { ctx -> + logger.info("Spongemock: Received request from ${ctx.req.requestURL}") ctx.json(TextResponse(200, ctx.body().toSpongemock())) - } + }) - post("/api/v1/uppercase") { ctx -> + post("/api/v1/uppercase", documented(document().operation { + it.description = "Responds with the UPPERCASED version of the string posted" + it.summary = "POST for UPPERCASING" + }.body().json("200")) { ctx -> + logger.info("Uppercase: Received request from ${ctx.req.requestURL}") ctx.json(TextResponse(200, ctx.body().toUpperCase())) - } + }) + } + + + Runtime.getRuntime().addShutdownHook(Thread { + app.stop() + }) + + app.events { event -> + event.serverStopping { logger.info("Server stopping") } + event.serverStopped { logger.info("Server stopped") } } } -fun handleSlackEvent(ctx: Context) { +private fun handleSlackEvent(ctx: Context) { // Short circuit for ssl check if ((ctx.formParam("ssl_check") ?: "0") == "1") { ctx.status(200) @@ -36,11 +74,11 @@ fun handleSlackEvent(ctx: Context) { } val responseObj = RespObj( - command = ctx.formParamMap().get("command")?.get(0).processToCommand(), - text = ctx.formParamMap().get("text")?.get(0) ?: "", - response_url = ctx.formParamMap().get("response_url")?.get(0) ?: "", - team_id = ctx.formParamMap().get("team_id")?.get(0) ?: "", - channel_id = ctx.formParamMap().get("channel_id")?.get(0) ?: "" + command = ctx.formParamMap()["command"]?.get(0).processToCommand(), + text = ctx.formParamMap()["text"]?.get(0) ?: "", + response_url = ctx.formParamMap()["response_url"]?.get(0) ?: "", + team_id = ctx.formParamMap()["team_id"]?.get(0) ?: "", + channel_id = ctx.formParamMap()["channel_id"]?.get(0) ?: "" ) sendResponse(ctx, responseObj) @@ -54,7 +92,7 @@ private fun String?.processToCommand(): Commands { } } -fun sendResponse(ctx: Context, respObj: RespObj) { +private fun sendResponse(ctx: Context, respObj: RespObj) { val returnMap = mutableMapOf() with(returnMap) { when (respObj.command) { @@ -71,4 +109,17 @@ fun sendResponse(ctx: Context, respObj: RespObj) { } ctx.json(returnMap) +} + +private fun createOpenApiOptions(): OpenApiOptions { + val o = { + OpenAPI() + .info(Info().version("0.1.2").description("Slackbot Open API documentation")) + .addServersItem(Server().url("https://acicchetti.dev/")) + .addServersItem(Server().url("http://localhost:7000/")) + } + + return OpenApiOptions(o) + .path("/swagger-docs") + .swagger(SwaggerOptions("/swagger").title("Slackbot Swagger Docs")) } \ No newline at end of file diff --git a/src/main/kotlin/com/anthonycicchetti/slackbot/utility/TextResponse.kt b/src/main/kotlin/com/anthonycicchetti/slackbot/utility/TextResponse.kt index 7fbcba3..1723749 100644 --- a/src/main/kotlin/com/anthonycicchetti/slackbot/utility/TextResponse.kt +++ b/src/main/kotlin/com/anthonycicchetti/slackbot/utility/TextResponse.kt @@ -1,3 +1,3 @@ package com.anthonycicchetti.slackbot.utility -data class TextResponse(val status: Int, val response: String) \ No newline at end of file +data class TextResponse(val status: Int = 200, val response: String) \ No newline at end of file