From 5db4a1ee8d734119d88c0c338738655f0f1c9b0d Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Sat, 24 Dec 2022 16:44:05 +0530 Subject: [PATCH] sync-backend: initialize barebones module This is simply an import of the project generated by start.ktor.io cleaned of its ungodly wildcard import fetish and better integrated into our existing build system. --- detekt-baselines/sync-backend.xml | 7 +++ gradle/libs.versions.toml | 16 +++++ settings.gradle.kts | 11 +++- sync-backend/build.gradle.kts | 45 ++++++++++++++ .../dev/msfjarvis/claw/sync/Application.kt | 29 +++++++++ .../dev/msfjarvis/claw/sync/plugins/HTTP.kt | 24 ++++++++ .../msfjarvis/claw/sync/plugins/Monitoring.kt | 28 +++++++++ .../msfjarvis/claw/sync/plugins/Routing.kt | 59 +++++++++++++++++++ .../msfjarvis/claw/sync/plugins/Security.kt | 58 ++++++++++++++++++ .../claw/sync/plugins/Serialization.kt | 22 +++++++ sync-backend/src/main/resources/logback.xml | 12 ++++ .../msfjarvis/claw/sync/ApplicationTest.kt | 29 +++++++++ 12 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 detekt-baselines/sync-backend.xml create mode 100644 sync-backend/build.gradle.kts create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/Application.kt create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/HTTP.kt create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Monitoring.kt create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Routing.kt create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Security.kt create mode 100644 sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Serialization.kt create mode 100644 sync-backend/src/main/resources/logback.xml create mode 100644 sync-backend/src/test/kotlin/dev/msfjarvis/claw/sync/ApplicationTest.kt diff --git a/detekt-baselines/sync-backend.xml b/detekt-baselines/sync-backend.xml new file mode 100644 index 00000000..be6d8fb9 --- /dev/null +++ b/detekt-baselines/sync-backend.xml @@ -0,0 +1,7 @@ + + + + + MagicNumber:HTTP.kt$443 + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 414b8dcf..1f2fb791 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ coroutines = "1.6.4" dagger = "2.44.2" kotest = "5.5.4" kotlin = "1.7.21" +ktor = "2.2.1" # @pin Needs to be aligned with Retrofit okhttp = "3.14.9" retrofit = "2.9.0" @@ -72,6 +73,20 @@ kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } +ktor-serialization-kotlinx-json-jvm = { module = "io.ktor:ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" } +ktor-server-auth-jvm = { module = "io.ktor:ktor-server-auth-jvm", version.ref = "ktor" } +ktor-server-call-id-jvm = { module = "io.ktor:ktor-server-call-id-jvm", version.ref = "ktor" } +ktor-server-call-logging-jvm = { module = "io.ktor:ktor-server-call-logging-jvm", version.ref = "ktor" } +ktor-server-content-negotiation-jvm = { module = "io.ktor:ktor-server-content-negotiation-jvm", version.ref = "ktor" } +ktor-server-core-jvm = { module = "io.ktor:ktor-server-core-jvm", version.ref = "ktor" } +ktor-server-host-common-jvm = { module = "io.ktor:ktor-server-host-common-jvm", version.ref = "ktor" } +ktor-server-hsts-jvm = { module = "io.ktor:ktor-server-hsts-jvm", version.ref = "ktor" } +ktor-server-http-redirect-jvm = { module = "io.ktor:ktor-server-http-redirect-jvm", version.ref = "ktor" } +ktor-server-locations-jvm = { module = "io.ktor:ktor-server-locations-jvm", version.ref = "ktor" } +ktor-server-netty-jvm = { module = "io.ktor:ktor-server-netty-jvm", version.ref = "ktor" } +ktor-server-status-pages-jvm = { module = "io.ktor:ktor-server-status-pages-jvm", version.ref = "ktor" } +ktor-server-tests-jvm = { module = "io.ktor:ktor-server-tests-jvm", version.ref = "ktor" } +logback-classic = "ch.qos.logback:logback-classic:1.2.11" napier = "io.github.aakira:napier:2.6.1" okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } @@ -88,5 +103,6 @@ whetstone = { module = "com.deliveryhero.whetstone:whetstone", version.ref = "wh android-test = { id = "com.android.test", version.ref = "agp" } anvil = "com.squareup.anvil:2.4.3" kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } whetstone = { id = "dev.msfjarvis.whetstone", version.ref = "whetstone" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1af67d9c..7647f3e0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,10 +30,16 @@ pluginManagement { forRepository { gradlePluginPortal() } filter { includeModule("com.github.ben-manes", "gradle-versions-plugin") + includeModule( + "com.github.johnrengelman.shadow", + "com.github.johnrengelman.shadow.gradle.plugin" + ) + includeModule("com.github.jengelman.gradle.plugins", "shadow") includeModule("org.gradle.android.cache-fix", "org.gradle.android.cache-fix.gradle.plugin") + includeModule("gradle.plugin.com.google.cloud.tools", "jib-gradle-plugin") includeModule("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin") - includeModule("com.sergei-lapin.napt", "com.sergei-lapin.napt.gradle.plugin") - includeModule("com.sergei-lapin.napt", "gradle") + includeModule("io.ktor.plugin", "io.ktor.plugin.gradle.plugin") + includeModule("io.ktor.plugin", "plugin") } } exclusiveContent { @@ -133,4 +139,5 @@ include( "database", "metadata-extractor", "model", + "sync-backend", ) diff --git a/sync-backend/build.gradle.kts b/sync-backend/build.gradle.kts new file mode 100644 index 00000000..33653b11 --- /dev/null +++ b/sync-backend/build.gradle.kts @@ -0,0 +1,45 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage") + +plugins { + application + kotlin("jvm") + alias(libs.plugins.kotlin.serialization) + id("dev.msfjarvis.claw.kotlin-common") + alias(libs.plugins.ktor) +} + +group = "dev.msfjarvis.claw" + +version = "0.0.1" + +application { + mainClass.set("dev.msfjarvis.claw.sync.ApplicationKt") + + val isDevelopment: Boolean = providers.gradleProperty("development").isPresent + applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment") +} + +dependencies { + implementation(libs.ktor.serialization.kotlinx.json.jvm) + implementation(libs.ktor.server.auth.jvm) + implementation(libs.ktor.server.call.id.jvm) + implementation(libs.ktor.server.call.logging.jvm) + implementation(libs.ktor.server.content.negotiation.jvm) + implementation(libs.ktor.server.core.jvm) + implementation(libs.ktor.server.host.common.jvm) + implementation(libs.ktor.server.hsts.jvm) + implementation(libs.ktor.server.http.redirect.jvm) + implementation(libs.ktor.server.locations.jvm) + implementation(libs.ktor.server.netty.jvm) + implementation(libs.ktor.server.status.pages.jvm) + implementation(libs.ktor.server.tests.jvm) + implementation(libs.logback.classic) + testImplementation(libs.kotest.assertions.core) + testImplementation(libs.kotest.runner.junit5) +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/Application.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/Application.kt new file mode 100644 index 00000000..5785ad9f --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/Application.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync + +import dev.msfjarvis.claw.sync.plugins.configureHTTP +import dev.msfjarvis.claw.sync.plugins.configureMonitoring +import dev.msfjarvis.claw.sync.plugins.configureRouting +import dev.msfjarvis.claw.sync.plugins.configureSecurity +import dev.msfjarvis.claw.sync.plugins.configureSerialization +import io.ktor.server.application.Application +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty + +fun main() { + embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) + .start(wait = true) +} + +fun Application.module() { + configureSerialization() + configureMonitoring() + configureHTTP() + configureSecurity() + configureRouting() +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/HTTP.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/HTTP.kt new file mode 100644 index 00000000..7e91b408 --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/HTTP.kt @@ -0,0 +1,24 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync.plugins + +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.plugins.hsts.HSTS +import io.ktor.server.plugins.httpsredirect.HttpsRedirect + +fun Application.configureHTTP() { + if (!developmentMode) { + install(HttpsRedirect) { + // The port to redirect to. By default 443, the default HTTPS port. + sslPort = 443 + // 301 Moved Permanently, or 302 Found redirect. + permanentRedirect = true + } + install(HSTS) { includeSubDomains = true } + } +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Monitoring.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Monitoring.kt new file mode 100644 index 00000000..20e5692b --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Monitoring.kt @@ -0,0 +1,28 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync.plugins + +import io.ktor.http.HttpHeaders +import io.ktor.server.application.Application +import io.ktor.server.application.install +import io.ktor.server.plugins.callid.CallId +import io.ktor.server.plugins.callid.callIdMdc +import io.ktor.server.plugins.callloging.CallLogging +import io.ktor.server.request.path +import org.slf4j.event.Level + +fun Application.configureMonitoring() { + install(CallLogging) { + level = Level.INFO + filter { call -> call.request.path().startsWith("/") } + callIdMdc("call-id") + } + install(CallId) { + header(HttpHeaders.XRequestId) + verify { callId: String -> callId.isNotEmpty() } + } +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Routing.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Routing.kt new file mode 100644 index 00000000..f5c78ee5 --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Routing.kt @@ -0,0 +1,59 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +@file:OptIn(KtorExperimentalLocationsAPI::class) + +package dev.msfjarvis.claw.sync.plugins + +import io.ktor.http.HttpStatusCode +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.locations.KtorExperimentalLocationsAPI +import io.ktor.server.locations.Location +import io.ktor.server.locations.Locations +import io.ktor.server.locations.get +import io.ktor.server.plugins.autohead.AutoHeadResponse +import io.ktor.server.plugins.doublereceive.DoubleReceive +import io.ktor.server.plugins.statuspages.StatusPages +import io.ktor.server.response.respond +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.routing + +fun Application.configureRouting() { + install(StatusPages) { + exception { call, _ -> call.respond(HttpStatusCode.Unauthorized) } + exception { call, _ -> call.respond(HttpStatusCode.Forbidden) } + } + install(Locations) {} + install(DoubleReceive) + install(AutoHeadResponse) + + routing { + get("/") { call.respondText("Hello World!") } + get { + call.respondText("Location: name=${it.name}, arg1=${it.arg1}, arg2=${it.arg2}") + } + // Register nested routes + get { call.respondText("Inside $it") } + get { call.respondText("Inside $it") } + } +} + +class AuthenticationException : RuntimeException() + +class AuthorizationException : RuntimeException() + +@Location("/location/{name}") +class MyLocation(val name: String, val arg1: Int = 42, val arg2: String = "default") + +@Location("/type/{name}") +data class Type(val name: String) { + @Location("/edit") data class Edit(val type: Type) + + @Location("/list/{page}") data class List(val type: Type, val page: Int) +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Security.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Security.kt new file mode 100644 index 00000000..f07a0e1a --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Security.kt @@ -0,0 +1,58 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync.plugins + +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.auth.UserIdPrincipal +import io.ktor.server.auth.authenticate +import io.ktor.server.auth.authentication +import io.ktor.server.auth.basic +import io.ktor.server.auth.form +import io.ktor.server.auth.principal +import io.ktor.server.response.respondText +import io.ktor.server.routing.get +import io.ktor.server.routing.routing + +fun Application.configureSecurity() { + + authentication { + basic(name = "myauth1") { + realm = "Ktor Server" + validate { credentials -> + if (credentials.name == credentials.password) { + UserIdPrincipal(credentials.name) + } else { + null + } + } + } + + form(name = "myauth2") { + userParamName = "user" + passwordParamName = "password" + challenge { + /**/ + } + } + } + + routing { + authenticate("myauth1") { + get("/protected/route/basic") { + val principal = call.principal() + call.respondText("Hello ${principal?.name}") + } + } + authenticate("myauth2") { + get("/protected/route/form") { + val principal = call.principal() + call.respondText("Hello ${principal?.name}") + } + } + } +} diff --git a/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Serialization.kt b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Serialization.kt new file mode 100644 index 00000000..879cbd04 --- /dev/null +++ b/sync-backend/src/main/kotlin/dev/msfjarvis/claw/sync/plugins/Serialization.kt @@ -0,0 +1,22 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync.plugins + +import io.ktor.serialization.kotlinx.json.json +import io.ktor.server.application.Application +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.plugins.contentnegotiation.ContentNegotiation +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing + +fun Application.configureSerialization() { + install(ContentNegotiation) { json() } + + routing { get("/json/kotlinx-serialization") { call.respond(mapOf("hello" to "world")) } } +} diff --git a/sync-backend/src/main/resources/logback.xml b/sync-backend/src/main/resources/logback.xml new file mode 100644 index 00000000..ef856580 --- /dev/null +++ b/sync-backend/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %X{call-id} %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/sync-backend/src/test/kotlin/dev/msfjarvis/claw/sync/ApplicationTest.kt b/sync-backend/src/test/kotlin/dev/msfjarvis/claw/sync/ApplicationTest.kt new file mode 100644 index 00000000..f1693635 --- /dev/null +++ b/sync-backend/src/test/kotlin/dev/msfjarvis/claw/sync/ApplicationTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright © 2022 Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.sync + +import dev.msfjarvis.claw.sync.plugins.configureRouting +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText +import io.ktor.http.HttpStatusCode +import io.ktor.server.testing.testApplication + +class ApplicationTest : FunSpec() { + init { + test("testRoot") { + testApplication { + application { configureRouting() } + client.get("/").apply { + status shouldBe HttpStatusCode.OK + bodyAsText() shouldBe "Hello World!" + } + } + } + } +}