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.
This commit is contained in:
Harsh Shandilya 2022-12-24 16:44:05 +05:30
parent b0bae12e6b
commit 5db4a1ee8d
12 changed files with 338 additions and 2 deletions

View file

@ -0,0 +1,7 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues></ManuallySuppressedIssues>
<CurrentIssues>
<ID>MagicNumber:HTTP.kt$443</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -9,6 +9,7 @@ coroutines = "1.6.4"
dagger = "2.44.2" dagger = "2.44.2"
kotest = "5.5.4" kotest = "5.5.4"
kotlin = "1.7.21" kotlin = "1.7.21"
ktor = "2.2.1"
# @pin Needs to be aligned with Retrofit # @pin Needs to be aligned with Retrofit
okhttp = "3.14.9" okhttp = "3.14.9"
retrofit = "2.9.0" 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-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-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", 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" napier = "io.github.aakira:napier:2.6.1"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-loggingInterceptor = { module = "com.squareup.okhttp3:logging-interceptor", 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" } android-test = { id = "com.android.test", version.ref = "agp" }
anvil = "com.squareup.anvil:2.4.3" anvil = "com.squareup.anvil:2.4.3"
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 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" } sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
whetstone = { id = "dev.msfjarvis.whetstone", version.ref = "whetstone" } whetstone = { id = "dev.msfjarvis.whetstone", version.ref = "whetstone" }

View file

@ -30,10 +30,16 @@ pluginManagement {
forRepository { gradlePluginPortal() } forRepository { gradlePluginPortal() }
filter { filter {
includeModule("com.github.ben-manes", "gradle-versions-plugin") 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("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("gradle.plugin.org.gradle.android", "android-cache-fix-gradle-plugin")
includeModule("com.sergei-lapin.napt", "com.sergei-lapin.napt.gradle.plugin") includeModule("io.ktor.plugin", "io.ktor.plugin.gradle.plugin")
includeModule("com.sergei-lapin.napt", "gradle") includeModule("io.ktor.plugin", "plugin")
} }
} }
exclusiveContent { exclusiveContent {
@ -133,4 +139,5 @@ include(
"database", "database",
"metadata-extractor", "metadata-extractor",
"model", "model",
"sync-backend",
) )

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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 }
}
}

View file

@ -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() }
}
}

View file

@ -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<AuthenticationException> { call, _ -> call.respond(HttpStatusCode.Unauthorized) }
exception<AuthorizationException> { call, _ -> call.respond(HttpStatusCode.Forbidden) }
}
install(Locations) {}
install(DoubleReceive)
install(AutoHeadResponse)
routing {
get("/") { call.respondText("Hello World!") }
get<MyLocation> {
call.respondText("Location: name=${it.name}, arg1=${it.arg1}, arg2=${it.arg2}")
}
// Register nested routes
get<Type.Edit> { call.respondText("Inside $it") }
get<Type.List> { 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)
}

View file

@ -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<UserIdPrincipal>()
call.respondText("Hello ${principal?.name}")
}
}
authenticate("myauth2") {
get("/protected/route/form") {
val principal = call.principal<UserIdPrincipal>()
call.respondText("Hello ${principal?.name}")
}
}
}
}

View file

@ -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")) } }
}

View file

@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %X{call-id} %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>

View file

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