aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.gradle.kts4
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt23
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt30
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt15
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt2
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt5
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt8
-rw-r--r--database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt17
-rw-r--r--server/core/build.gradle.kts31
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt34
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt25
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/model.kt30
-rw-r--r--server/core/src/main/resources/application.conf10
-rw-r--r--server/core/test-requests/test-hello.http5
-rw-r--r--settings.gradle.kts1
15 files changed, 226 insertions, 14 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index 171c811..8377205 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,7 +2,9 @@ import com.github.gmazzo.buildconfig.BuildConfigExtension
import java.io.ByteArrayOutputStream
plugins {
- kotlin("jvm") version "2.0.20" apply false
+ val kotlinVersion = "2.0.20"
+ kotlin("jvm") version kotlinVersion apply false
+ kotlin("plugin.serialization") version kotlinVersion apply false
id("com.github.gmazzo.buildconfig") version "5.5.0" apply false
id("ledger-globals")
id("com.github.johnrengelman.shadow") version "8.1.1" apply false
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
index d1294f7..33727de 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Column.kt
@@ -1,10 +1,31 @@
package moe.nea.ledger.database
+import moe.nea.ledger.database.sql.IntoSelectable
+import moe.nea.ledger.database.sql.Selectable
+import java.sql.PreparedStatement
+
class Column<T> @Deprecated("Use Table.column instead") constructor(
val table: Table,
val name: String,
val type: DBType<T>
-) {
+) : IntoSelectable<T> {
+ override fun asSelectable() = object : Selectable<T> {
+ override fun asSql(): String {
+ return qualifiedSqlName
+ }
+
+ override val dbType: DBType<T>
+ get() = this@Column.type
+
+ override fun guessColumn(): Column<T>? {
+ return this@Column
+ }
+
+ override fun appendToStatement(stmt: PreparedStatement, startIndex: Int): Int {
+ return startIndex
+ }
+ }
+
val sqlName get() = "`$name`"
val qualifiedSqlName get() = table.sqlName + "." + sqlName
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
index 7829fbb..e58eef4 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Query.kt
@@ -3,19 +3,22 @@ package moe.nea.ledger.database
import moe.nea.ledger.database.sql.ANDExpression
import moe.nea.ledger.database.sql.BooleanExpression
import moe.nea.ledger.database.sql.Clause
+import moe.nea.ledger.database.sql.IntoSelectable
import moe.nea.ledger.database.sql.Join
import moe.nea.ledger.database.sql.SQLQueryComponent
import moe.nea.ledger.database.sql.SQLQueryGenerator.concatToFilledPreparedStatement
+import moe.nea.ledger.database.sql.Selectable
import java.sql.Connection
class Query(
val connection: Connection,
- val selectedColumns: MutableList<Column<*>>,
+ val selectedColumns: MutableList<Selectable<*>>,
var table: Table,
var limit: UInt? = null,
var skip: UInt? = null,
val joins: MutableList<Join> = mutableListOf(),
val conditions: MutableList<BooleanExpression> = mutableListOf(),
+ var distinct: Boolean = false,
// var order: OrderClause?= null,
) : Iterable<ResultRow> {
fun join(table: Table, on: Clause): Query {
@@ -28,8 +31,10 @@ class Query(
return this
}
- fun select(vararg columns: Column<*>): Query {
- selectedColumns.addAll(columns)
+ fun select(vararg columns: IntoSelectable<*>): Query {
+ for (column in columns) {
+ this.selectedColumns.add(column.asSelectable())
+ }
return this
}
@@ -39,16 +44,29 @@ class Query(
return this
}
+ fun distinct(): Query {
+ this.distinct = true
+ return this
+ }
+
fun limit(limit: UInt): Query {
this.limit = limit
return this
}
override fun iterator(): Iterator<ResultRow> {
- val columnSelections = selectedColumns.joinToString { it.qualifiedSqlName }
val elements = mutableListOf(
- SQLQueryComponent.standalone("SELECT $columnSelections FROM ${table.sqlName}"),
+ SQLQueryComponent.standalone("SELECT"),
)
+ if (distinct)
+ elements.add(SQLQueryComponent.standalone("DISTINCT"))
+ selectedColumns.forEachIndexed { idx, it ->
+ elements.add(it)
+ if (idx != selectedColumns.lastIndex) {
+ elements.add(SQLQueryComponent.standalone(","))
+ }
+ }
+ elements.add(SQLQueryComponent.standalone("FROM ${table.sqlName}"))
elements.addAll(joins)
if (conditions.any()) {
elements.add(SQLQueryComponent.standalone("WHERE"))
@@ -84,7 +102,7 @@ class Query(
}
hasAdvanced = false
return ResultRow(selectedColumns.withIndex().associate {
- it.value to it.value.type.get(results, it.index + 1)
+ it.value to it.value.dbType.get(results, it.index + 1)
})
}
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
index d92f913..6715f27 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/ResultRow.kt
@@ -1,9 +1,22 @@
package moe.nea.ledger.database
-class ResultRow(val columnValues: Map<Column<*>, *>) {
+import moe.nea.ledger.database.sql.Selectable
+
+class ResultRow(val selectableValues: Map<Selectable<*>, *>) {
+ val columnValues = selectableValues.mapNotNull {
+ val col = it.key.guessColumn() ?: return@mapNotNull null
+ col to it.value
+ }.toMap()
+
operator fun <T> get(column: Column<T>): T {
val value = columnValues[column]
?: error("Invalid column ${column.name}. Only ${columnValues.keys.joinToString { it.name }} are available.")
return value as T
}
+
+ operator fun <T> get(column: Selectable<T>): T {
+ val value = selectableValues[column]
+ ?: error("Invalid selectable ${column}. Only ${selectableValues.keys} are available.")
+ return value as T
+ }
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
index c136f48..61dc8f0 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/Table.kt
@@ -98,6 +98,6 @@ abstract class Table(val name: String) {
}
fun selectAll(connection: Connection): Query {
- return Query(connection, columns.toMutableList(), this)
+ return Query(connection, columns.mapTo(mutableListOf()) { it.asSelectable() }, this)
}
} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt
new file mode 100644
index 0000000..0068f6b
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/IntoSelectable.kt
@@ -0,0 +1,5 @@
+package moe.nea.ledger.database.sql
+
+interface IntoSelectable<T> {
+ fun asSelectable(): Selectable<T>
+} \ No newline at end of file
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
index be81ff2..2eb54fd 100644
--- a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/SQLQueryGenerator.kt
@@ -6,14 +6,14 @@ import java.sql.PreparedStatement
object SQLQueryGenerator {
fun List<SQLQueryComponent>.concatToFilledPreparedStatement(connection: Connection): PreparedStatement {
- var query = ""
+ val query = StringBuilder()
for (element in this) {
if (query.isNotEmpty()) {
- query += " "
+ query.append(" ")
}
- query += element.asSql()
+ query.append(element.asSql())
}
- val statement = connection.prepareAndLog(query)
+ val statement = connection.prepareAndLog(query.toString())
var index = 1
for (element in this) {
val nextIndex = element.appendToStatement(statement, index)
diff --git a/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt
new file mode 100644
index 0000000..a95b66b
--- /dev/null
+++ b/database/core/src/main/kotlin/moe/nea/ledger/database/sql/Selectable.kt
@@ -0,0 +1,17 @@
+package moe.nea.ledger.database.sql
+
+import moe.nea.ledger.database.Column
+import moe.nea.ledger.database.DBType
+
+/**
+ * Something that can be selected. Like a column, or an expression thereof
+ */
+interface Selectable<T> : SQLQueryComponent, IntoSelectable<T> {
+ override fun asSelectable(): Selectable<T> {
+ return this
+ }
+
+ val dbType: DBType<T>
+ fun guessColumn(): Column<T>?
+}
+
diff --git a/server/core/build.gradle.kts b/server/core/build.gradle.kts
new file mode 100644
index 0000000..87f613a
--- /dev/null
+++ b/server/core/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ kotlin("jvm")
+ kotlin("plugin.serialization")
+ application
+}
+
+val ktor_version = "3.0.3"
+
+dependencies {
+ implementation(platform("io.ktor:ktor-bom:$ktor_version"))
+ implementation("io.ktor:ktor-server-netty")
+ implementation("io.ktor:ktor-server-status-pages")
+ implementation("io.ktor:ktor-server-content-negotiation")
+ implementation("io.ktor:ktor-server-openapi")
+ implementation("io.ktor:ktor-serialization-kotlinx-json")
+ implementation("io.ktor:ktor-server-compression")
+ implementation(project(":database:impl"))
+
+ runtimeOnly("ch.qos.logback:logback-classic:1.5.16")
+ runtimeOnly("org.xerial:sqlite-jdbc:3.45.3.0")
+}
+
+java {
+ toolchain.languageVersion.set(JavaLanguageVersion.of(21))
+}
+application {
+ val isDevelopment: Boolean = project.ext.has("development")
+ applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment",
+ "-Dledger.databasefolder=${project(":mod").file("run/money-ledger").absoluteFile}")
+ mainClass.set("moe.nea.ledger.server.core.ApplicationKt")
+}
diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt
new file mode 100644
index 0000000..0ea6ed3
--- /dev/null
+++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/Application.kt
@@ -0,0 +1,34 @@
+package moe.nea.ledger.server.core
+
+import io.ktor.serialization.kotlinx.json.json
+import io.ktor.server.application.Application
+import io.ktor.server.application.install
+import io.ktor.server.netty.EngineMain
+import io.ktor.server.plugins.compression.Compression
+import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.server.routing.route
+import io.ktor.server.routing.routing
+import moe.nea.ledger.database.Database
+import moe.nea.ledger.server.core.api.apiRouting
+import java.io.File
+
+fun main(args: Array<String>) {
+ EngineMain.main(args)
+}
+
+
+fun Application.module() {
+ install(Compression)
+ install(ContentNegotiation) {
+ json()
+// cbor()
+ }
+ val database = Database(File(System.getProperty("ledger.databasefolder")))
+ database.loadAndUpgrade()
+ routing {
+ route("/api") {
+ this.apiRouting(database)
+ }
+ }
+}
+
diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt
new file mode 100644
index 0000000..264f74b
--- /dev/null
+++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt
@@ -0,0 +1,25 @@
+package moe.nea.ledger.server.core.api
+
+import io.ktor.server.response.respond
+import io.ktor.server.response.respondText
+import io.ktor.server.routing.Route
+import io.ktor.server.routing.Routing
+import io.ktor.server.routing.get
+import moe.nea.ledger.database.DBLogEntry
+import moe.nea.ledger.database.Database
+import moe.nea.ledger.server.core.Profile
+
+fun Route.apiRouting(database: Database) {
+ get("/") {
+ call.respondText("K")
+ }
+ get("/profiles") {
+ val profiles = DBLogEntry.from(database.connection)
+ .select(DBLogEntry.playerId, DBLogEntry.profileId)
+ .distinct()
+ .map {
+ Profile(it[DBLogEntry.playerId], it[DBLogEntry.profileId])
+ }
+ call.respond(profiles)
+ }
+}
diff --git a/server/core/src/main/kotlin/moe/nea/ledger/server/core/model.kt b/server/core/src/main/kotlin/moe/nea/ledger/server/core/model.kt
new file mode 100644
index 0000000..a27a729
--- /dev/null
+++ b/server/core/src/main/kotlin/moe/nea/ledger/server/core/model.kt
@@ -0,0 +1,30 @@
+@file:UseSerializers(UUIDSerializer::class)
+
+package moe.nea.ledger.server.core
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.UseSerializers
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import java.util.UUID
+
+object UUIDSerializer : KSerializer<UUID> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("LedgerUUID", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: kotlinx.serialization.encoding.Decoder): UUID {
+ return UUID.fromString(decoder.decodeString())
+ }
+
+ override fun serialize(encoder: kotlinx.serialization.encoding.Encoder, value: UUID) {
+ encoder.encodeString(value.toString())
+ }
+}
+
+@Serializable
+data class Profile(
+ val playerId: UUID,
+ val profileId: UUID,
+) \ No newline at end of file
diff --git a/server/core/src/main/resources/application.conf b/server/core/src/main/resources/application.conf
new file mode 100644
index 0000000..386ffd3
--- /dev/null
+++ b/server/core/src/main/resources/application.conf
@@ -0,0 +1,10 @@
+ktor {
+ application {
+ modules = [
+ moe.nea.ledger.server.core.ApplicationKt.module
+ ]
+ }
+ deployment {
+ port = 8080
+ }
+} \ No newline at end of file
diff --git a/server/core/test-requests/test-hello.http b/server/core/test-requests/test-hello.http
new file mode 100644
index 0000000..3ddf352
--- /dev/null
+++ b/server/core/test-requests/test-hello.http
@@ -0,0 +1,5 @@
+### GET request to example server
+GET localhost:8080/
+
+### GET profiles
+GET localhost:8080/api/profiles \ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 19f404d..d34446f 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -31,4 +31,5 @@ include("database:core")
include("database:impl")
include("basetypes")
include("mod")
+include("server:core")
includeBuild("build-src")