diff --git a/build.gradle.kts b/build.gradle.kts index 39737f2..079ef39 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,4 +7,5 @@ plugins { alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.hilt.android) apply false alias(libs.plugins.ksp) apply false + alias(libs.plugins.ktor) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b33b32c..c5cbc7a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,6 +16,8 @@ navigationCompose = "2.8.5" kotlinxSerialization = "1.7.3" kotlinxCoroutines = "1.9.0" mockk = "1.13.13" +ktor = "3.1.2" +logback = "1.5.18" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -45,6 +47,13 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" } kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } +ktor-server-core = { group = "io.ktor", name = "ktor-server-core", version.ref = "ktor" } +ktor-server-netty = { group = "io.ktor", name = "ktor-server-netty", version.ref = "ktor" } +ktor-server-content-negotiation = { group = "io.ktor", name = "ktor-server-content-negotiation", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-server-config-yaml = { group = "io.ktor", name = "ktor-server-config-yaml", version.ref = "ktor" } +ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } +logback-classic = { group = "ch.qos.logback", name = "logback-classic", version.ref = "logback" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } @@ -54,3 +63,4 @@ kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "ko kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +ktor = { id = "io.ktor.plugin", version.ref = "ktor" } diff --git a/server/build.gradle.kts b/server/build.gradle.kts new file mode 100644 index 0000000..64a467e --- /dev/null +++ b/server/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ktor) +} + +application { + mainClass.set("de.krisenvorrat.server.ApplicationKt") +} + +ktor { + fatJar { + archiveFileName.set("server.jar") + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 +} + +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) + } +} + +dependencies { + implementation(project(":shared")) + + implementation(libs.ktor.server.core) + implementation(libs.ktor.server.netty) + implementation(libs.ktor.server.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.logback.classic) + + testImplementation(libs.ktor.server.test.host) + testImplementation(libs.junit) + testImplementation(libs.kotlinx.serialization.json) +} diff --git a/server/src/main/kotlin/de/krisenvorrat/server/Application.kt b/server/src/main/kotlin/de/krisenvorrat/server/Application.kt new file mode 100644 index 0000000..c355081 --- /dev/null +++ b/server/src/main/kotlin/de/krisenvorrat/server/Application.kt @@ -0,0 +1,15 @@ +package de.krisenvorrat.server + +import de.krisenvorrat.server.plugins.configureRouting +import de.krisenvorrat.server.plugins.configureSerialization +import io.ktor.server.application.* +import io.ktor.server.netty.* + +fun main(args: Array) { + EngineMain.main(args) +} + +internal fun Application.module() { + configureSerialization() + configureRouting() +} diff --git a/server/src/main/kotlin/de/krisenvorrat/server/plugins/Routing.kt b/server/src/main/kotlin/de/krisenvorrat/server/plugins/Routing.kt new file mode 100644 index 0000000..39ae70f --- /dev/null +++ b/server/src/main/kotlin/de/krisenvorrat/server/plugins/Routing.kt @@ -0,0 +1,14 @@ +package de.krisenvorrat.server.plugins + +import io.ktor.http.* +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +internal fun Application.configureRouting() { + routing { + get("/health") { + call.respondText("OK", ContentType.Text.Plain, HttpStatusCode.OK) + } + } +} diff --git a/server/src/main/kotlin/de/krisenvorrat/server/plugins/Serialization.kt b/server/src/main/kotlin/de/krisenvorrat/server/plugins/Serialization.kt new file mode 100644 index 0000000..e839293 --- /dev/null +++ b/server/src/main/kotlin/de/krisenvorrat/server/plugins/Serialization.kt @@ -0,0 +1,16 @@ +package de.krisenvorrat.server.plugins + +import io.ktor.serialization.kotlinx.json.* +import io.ktor.server.application.* +import io.ktor.server.plugins.contentnegotiation.* +import kotlinx.serialization.json.Json + +internal fun Application.configureSerialization() { + install(ContentNegotiation) { + json(Json { + prettyPrint = true + isLenient = false + ignoreUnknownKeys = true + }) + } +} diff --git a/server/src/main/resources/application.conf b/server/src/main/resources/application.conf new file mode 100644 index 0000000..23b17b8 --- /dev/null +++ b/server/src/main/resources/application.conf @@ -0,0 +1,9 @@ +ktor { + deployment { + port = 8080 + host = "0.0.0.0" + } + application { + modules = [ de.krisenvorrat.server.ApplicationKt.module ] + } +} diff --git a/server/src/main/resources/logback.xml b/server/src/main/resources/logback.xml new file mode 100644 index 0000000..259134b --- /dev/null +++ b/server/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt new file mode 100644 index 0000000..7ce8d99 --- /dev/null +++ b/server/src/test/kotlin/de/krisenvorrat/server/ApplicationTest.kt @@ -0,0 +1,38 @@ +package de.krisenvorrat.server + +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.* +import io.ktor.server.testing.* +import org.junit.Assert.assertEquals +import org.junit.Test + +class ApplicationTest { + + @Test + fun test_healthEndpoint_returnsOk() = testApplication { + application { + module() + } + + // When + val response = client.get("/health") + + // Then + assertEquals(HttpStatusCode.OK, response.status) + assertEquals("OK", response.bodyAsText()) + } + + @Test + fun test_unknownRoute_returns404() = testApplication { + application { + module() + } + + // When + val response = client.get("/nonexistent") + + // Then + assertEquals(HttpStatusCode.NotFound, response.status) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index fc907db..8de6a98 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,3 +22,4 @@ dependencyResolutionManagement { rootProject.name = "krisenvorrat" include(":app") include(":shared") +include(":server")