Compare commits

..

4 Commits

Author SHA1 Message Date
calboo
b77f2fa371 patching values 2025-11-30 21:59:05 +01:00
calboo
7a100c96c9 settzing an getting values 2025-11-30 21:52:51 +01:00
calboo
ccd5225c3b test route 2025-11-30 21:00:48 +01:00
calboo
8207d09703 dependency update 2025-11-30 20:25:35 +01:00
15 changed files with 479 additions and 12 deletions

View File

@@ -1,5 +1,7 @@
plugins { plugins {
kotlin("jvm") version "2.1.20" kotlin("jvm") version "2.1.20"
id("io.ktor.plugin") version "2.3.4"
kotlin("plugin.serialization") version "1.9.0"
} }
group = "org.calvin.erfmann" group = "org.calvin.erfmann"
@@ -11,6 +13,22 @@ repositories {
dependencies { dependencies {
testImplementation(kotlin("test")) testImplementation(kotlin("test"))
// Ktor server dependencies
implementation("io.ktor:ktor-server-netty:2.3.4")
implementation("io.ktor:ktor-server-core:2.3.4")
implementation("io.ktor:ktor-server-content-negotiation:2.3.4")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
//Logging
implementation("ch.qos.logback:logback-classic:1.5.13")
//Ktor Websockets and CORS
implementation("io.ktor:ktor-server-websockets:2.3.4")
implementation("io.ktor:ktor-server-cors:2.3.4")
implementation("io.ktor:ktor-client-core:2.3.4")
implementation("io.ktor:ktor-client-cio:2.3.4")
implementation("io.ktor:ktor-client-content-negotiation:2.3.4")
} }
tasks.test { tasks.test {
@@ -19,3 +37,23 @@ tasks.test {
kotlin { kotlin {
jvmToolchain(23) jvmToolchain(23)
} }
tasks.register<Jar>("fatJar") {
group = "build"
description = "Assemble a fat jar with all dependencies."
archiveClassifier.set("all")
manifest {
attributes["Main-Class"] = "MainKt" // ggf. anpassen auf dein Main Package
}
from(sourceSets.main.get().output)
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
})
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

View File

@@ -1,16 +1,16 @@
package org.calvin.erfmann package org.calvin.erfmann
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
fun main() {
val name = "Kotlin"
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
println("Hello, " + name + "!")
for (i in 1..5) {
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>. import org.calvin.erfmann.api.apiServ
println("i = $i")
} val apiServ = apiServ()
fun main() {
apiServ.startServer()
} }

View File

@@ -0,0 +1,55 @@
package org.calvin.erfmann.api
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.application.install
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.websocket.WebSockets
import io.ktor.server.websocket.pingPeriod
import io.ktor.server.websocket.timeout
import kotlinx.serialization.json.Json
import org.calvin.erfmann.api.plugins.configureRouting
import org.calvin.erfmann.stuff.authService
import org.calvin.erfmann.theGoodStuff.PoolManager
import java.time.Duration
class apiServ {
var authService = authService("password")
var poolManager = PoolManager()
fun startServer(){
embeddedServer(Netty, port = 9000, host = "0.0.0.0") {
module()
}.start(wait = true)
}
fun Application.module() {
install(ContentNegotiation) { // SERVER Plugin mit Alias
json()
}
install(WebSockets.Plugin) {
pingPeriod = Duration.ofSeconds(15)
timeout = Duration.ofSeconds(30)
maxFrameSize = Long.MAX_VALUE
masking = false
}
configureRouting(
authService,
poolManager
)
}
}

View File

@@ -0,0 +1,31 @@
package org.calvin.erfmann.api.plugins
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import io.ktor.server.application.Application
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
import io.ktor.server.routing.routing
import kotlinx.serialization.json.Json
import org.calvin.erfmann.api.routes.getValue
import org.calvin.erfmann.api.routes.helloRoutes
import org.calvin.erfmann.api.routes.login
import org.calvin.erfmann.api.routes.patchValue
import org.calvin.erfmann.api.routes.setValue
import org.calvin.erfmann.stuff.authService
import org.calvin.erfmann.theGoodStuff.PoolManager
fun Application.configureRouting(authService: authService, poolManager: PoolManager) {
routing {
helloRoutes()
login(authService)
getValue(authService, poolManager)
setValue(authService, poolManager)
patchValue(authService, poolManager)
}
}

View File

@@ -0,0 +1,40 @@
package org.calvin.erfmann.api.routes
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import kotlinx.serialization.Serializable
import org.calvin.erfmann.stuff.authService
import org.calvin.erfmann.theGoodStuff.PoolManager
@Serializable
data class GetValueRequest(val token: String, val pool: String, val key: String,)
fun Route.getValue(authService: authService, poolManager: PoolManager) {
get("/value") {
val request = call.receive<GetValueRequest>()
val isValid = authService.isTokenValid(request.token)
if (isValid) {
val pool = poolManager.getPool(request.pool)
if (pool != null) {
val value = pool.getValueValue(request.key)
if (value != null) {
call.respond(mapOf("value" to value))
} else {
call.respond(mapOf("error" to "Key not found"))
}
} else {
call.respond(mapOf("error" to "Pool not found"))
}
} else {
call.respond(mapOf("error" to "Invalid token"))
}
}
}

View File

@@ -0,0 +1,22 @@
package org.calvin.erfmann.api.routes
import io.ktor.server.application.call
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
fun Route.helloRoutes() {
route("/hello") {
get {
call.respond(mapOf("message" to "hallo du knecht"))
}
}
}

View File

@@ -0,0 +1,31 @@
package org.calvin.erfmann.api.routes
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import kotlinx.serialization.Serializable
import org.calvin.erfmann.stuff.authService
@Serializable
data class LoginRequest(val password: String)
fun Route.login(authService: authService) {
post("/login") {
val request = call.receive<LoginRequest>()
val token = authService.getToken(request.password)
if (token != null) {
call.respond(mapOf("token" to token))
} else {
call.respond(mapOf("error" to "Invalid password"))
}
}
}

View File

@@ -0,0 +1,41 @@
package org.calvin.erfmann.api.routes
import io.ktor.server.routing.patch
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.serialization.Serializable
import org.calvin.erfmann.stuff.authService
import org.calvin.erfmann.theGoodStuff.PoolManager
@Serializable
data class PatchValueRequest(val token: String, val pool: String, val key: String, val value: String)
fun Route.patchValue(authService: authService, poolManager: PoolManager) {
patch("/value") {
val request = call.receive<SetValueRequest>()
val isValid = authService.isTokenValid(request.token)
if (isValid) {
val pool = poolManager.getPool(request.pool)
if (pool != null) {
pool.setValueValue(request.key, request.value)
call.respond(mapOf("status" to "success"))
} else {
call.respond(mapOf("error" to "Pool not found"))
}
} else {
call.respond(mapOf("error" to "Invalid token"))
}
}
}

View File

@@ -0,0 +1,38 @@
package org.calvin.erfmann.api.routes
import io.ktor.server.application.call
import io.ktor.server.request.receive
import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.post
import kotlinx.serialization.Serializable
import org.calvin.erfmann.stuff.authService
import org.calvin.erfmann.theGoodStuff.PoolManager
@Serializable
data class SetValueRequest(val token: String, val pool: String, val key: String, val value: String)
fun Route.setValue(authService: authService, poolManager: PoolManager) {
post("/value") {
val request = call.receive<SetValueRequest>()
val isValid = authService.isTokenValid(request.token)
if (isValid) {
val pool = poolManager.getPool(request.pool)
if (pool != null) {
pool.setValueValue(request.key, request.value)
call.respond(mapOf("status" to "success"))
} else {
poolManager.createPool(request.pool).setValueValue(request.key, request.value)
call.respond(mapOf("status" to "success"))
}
} else {
call.respond(mapOf("error" to "Invalid token"))
}
}
}

View File

@@ -0,0 +1,18 @@
package org.calvin.erfmann.api.utils
import java.security.SecureRandom
class tokenGenerator {
private val CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
private val secureRandom = SecureRandom()
/**
* Generiert einen sicheren Token.
* @param length Standardmäßig 32 Zeichen, kann aber angepasst werden.
*/
fun generate(length: Int = 32): String {
return (1..length)
.map { CHARACTERS[secureRandom.nextInt(CHARACTERS.length)] }
.joinToString("")
}
}

View File

@@ -0,0 +1,30 @@
package org.calvin.erfmann.stuff
import org.calvin.erfmann.api.utils.tokenGenerator
class authService(password: String) {
val password: String = password
var activeTokens = mutableListOf<String>()
fun getToken(inputPassword: String): String?{
if (inputPassword != password){
return null
}
val token = tokenGenerator().generate()
activeTokens.add(token)
return token
}
fun isTokenValid(token: String): Boolean{
return activeTokens.contains(token)
}
}

View File

@@ -0,0 +1,46 @@
package org.calvin.erfmann.theGoodStuff
class Pool(initName: String) {
var name: String = initName
var values: MutableMap<String, Value> = mutableMapOf()
fun getValueValue(key: String): String? {
val value = values[key]
return value?.getValueValue()
}
fun setValueValue(key: String, newValue: String) {
val value = values[key]
if (value != null) {
value.setValueValue(newValue)
} else {
values[key] = Value(key, newValue)
}
}
fun updateValueValue(key: String, newValue: String) {
val value = values[key]
if (value != null) {
value.setValueValue(newValue)
}
}
fun getVersion(key: String, version: Long): String? {
val value = values[key]
return value?.getVersion(version)
}
fun deleteValue(key: String) {
values.remove(key)
}
fun isKeyPresent(key: String): Boolean {
return values.containsKey(key)
}
}

View File

@@ -0,0 +1,24 @@
package org.calvin.erfmann.theGoodStuff
class PoolManager{
var pools: MutableMap<String, Pool> = mutableMapOf()
fun getPool(poolName: String): Pool? {
return pools[poolName]
}
fun createPool(poolName: String): Pool {
val pool = Pool(poolName)
pools[poolName] = pool
return pool
}
fun deletePool(poolName: String) {
pools.remove(poolName)
}
fun isPoolPresent(poolName: String): Boolean {
return pools.containsKey(poolName)
}
}

View File

@@ -0,0 +1,38 @@
package org.calvin.erfmann.theGoodStuff
class Value(initKey: String, initValue: String) {
// Das einfachste Value Element
var key: String = initKey
var value: String = initValue
var lastUpdated: Long = System.currentTimeMillis()
var createdAt: Long = System.currentTimeMillis()
var lastAcccessed: Long = System.currentTimeMillis()
var currentVersion: Long = 0
var versions = mutableListOf<ValueVersion>()
fun getValueValue(): String {
this.lastAcccessed = System.currentTimeMillis()
return this.value
}
fun getCurrentValueVersion(): Long {
return this.currentVersion
}
fun setValueValue(newValue: String) {
versions.add(ValueVersion(this.value , this.currentVersion))
this.value = newValue
this.lastUpdated = System.currentTimeMillis()
this.currentVersion += 1
}
fun getVersion(version: Long): String? {
val ver = versions.find { it.version == version }
return ver?.getValueValue()
}
}

View File

@@ -0,0 +1,15 @@
package org.calvin.erfmann.theGoodStuff
class ValueVersion(initValue: String, initVersion: Long) {
// Eine Version eines Values
var value: String = initValue
var lastAcccessed: Long = 1
var version : Long = initVersion
fun getValueValue(): String {
this.lastAcccessed = System.currentTimeMillis()
return this.value
}
}