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!"
+ }
+ }
+ }
+ }
+}