aboutsummaryrefslogtreecommitdiff
path: root/server/core/src
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-01-22 01:10:10 +0100
committerLinnea Gräf <nea@nea.moe>2025-01-22 01:10:10 +0100
commit6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa (patch)
tree1a49a6aeb9e7f901ede729f1fed9d1d230dadc87 /server/core/src
parent550441921eed03b88ec94bea10deb1c45ef6e17b (diff)
downloadLocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.tar.gz
LocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.tar.bz2
LocalTransactionLedger-6f148df84dfe5d0d0d1c6a0614f86e374fc8d1aa.zip
feat(server): Add first analysis
Diffstat (limited to 'server/core/src')
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt109
1 files changed, 88 insertions, 21 deletions
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
index 4bc6472..3240a65 100644
--- 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
@@ -6,8 +6,10 @@ import io.ktor.server.response.respond
import io.ktor.server.routing.Route
import io.ktor.server.routing.get
import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
+import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
@@ -19,14 +21,29 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import moe.nea.ledger.ItemChange
import moe.nea.ledger.TransactionType
+import moe.nea.ledger.analysis.Analysis
+import moe.nea.ledger.analysis.AnalysisFilter
+import moe.nea.ledger.analysis.AnalysisResult
import moe.nea.ledger.database.DBItemEntry
import moe.nea.ledger.database.DBLogEntry
import moe.nea.ledger.database.Database
import moe.nea.ledger.database.sql.Clause
import moe.nea.ledger.server.core.Profile
import moe.nea.ledger.utils.ULIDWrapper
+import java.time.Instant
+import java.util.ServiceLoader
+import java.util.UUID
fun Route.apiRouting(database: Database) {
+ val allOfferedAnalysisServices: Map<String, Analysis> = run {
+ val serviceLoader = ServiceLoader.load(Analysis::class.java, environment.classLoader)
+ val map = mutableMapOf<String, Analysis>()
+ serviceLoader.forEach {
+ map[it.id] = it
+ }
+ map
+ }
+
get("/profiles") {
val profiles = DBLogEntry.from(database.connection)
.select(DBLogEntry.playerId, DBLogEntry.profileId)
@@ -49,6 +66,48 @@ fun Route.apiRouting(database: Database) {
Url("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/data/item_names.json")
Json.decodeFromStream<Map<String, String>>(itemNamesUrl.toURI().toURL().openStream())
}
+ get("/analysis/execute") {
+ val analysis = allOfferedAnalysisServices[call.queryParameters["analysis"]] ?: TODO()
+ val start = call.queryParameters["tStart"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } ?: TODO()
+ val end = call.queryParameters["tEnd"]?.toLongOrNull()?.let { Instant.ofEpochMilli(it) } ?: TODO()
+ val analysisResult = withContext(Dispatchers.IO) {
+ analysis.perform(
+ database.connection,
+ object : AnalysisFilter {
+ override val startWindow: Instant
+ get() = start
+ override val endWindow: Instant
+ get() = end
+ override val profiles: List<UUID>
+ get() = listOf()
+ }
+ )
+ }
+ call.respond(analysisResult)
+ }.docs {
+ summary = "Execute an analysis on a given timeframe"
+ operationId = "executeAnalysis"
+ queryParameter<String>("analysis", description = "An analysis id obtained from getAnalysis")
+ queryParameter<Long>("tStart", description = "The start of the timeframe to analyze")
+ queryParameter<Long>("tEnd",
+ description = "The end of the timeframe to analyze. Make sure to use the end of the day if you want the entire day included.")
+ tag(Tags.DATA)
+ respondsOk {
+ schema<AnalysisResult>()
+ }
+ }
+ get("/analysis/list") {
+ call.respond(allOfferedAnalysisServices.values.map {
+ AnalysisListing(it.name, it.id)
+ })
+ }.docs {
+ summary = "List all installed analysis"
+ operationId = "getAnalysis"
+ tag(Tags.DATA)
+ respondsOk {
+ schema<List<AnalysisListing>>()
+ }
+ }
get("/item") {
val itemIds = call.queryParameters.getAll("itemId")?.toSet() ?: emptySet()
val itemNameMap = itemNames.await()
@@ -65,28 +124,30 @@ fun Route.apiRouting(database: Database) {
get("/entries") {
val logs = mutableMapOf<ULIDWrapper, LogEntry>()
val items = mutableMapOf<ULIDWrapper, MutableList<SerializableItemChange>>()
- DBLogEntry.from(database.connection)
- .join(DBItemEntry, Clause { column(DBItemEntry.transactionId) eq column(DBLogEntry.transactionId) })
- .select(DBLogEntry.profileId,
- DBLogEntry.playerId,
- DBLogEntry.transactionId,
- DBLogEntry.type,
- DBItemEntry.mode,
- DBItemEntry.itemId,
- DBItemEntry.size)
- .forEach { row ->
- logs.getOrPut(row[DBLogEntry.transactionId]) {
- LogEntry(row[DBLogEntry.type],
- row[DBLogEntry.transactionId],
- listOf())
+ withContext(Dispatchers.IO) {
+ DBLogEntry.from(database.connection)
+ .join(DBItemEntry, Clause { column(DBItemEntry.transactionId) eq column(DBLogEntry.transactionId) })
+ .select(DBLogEntry.profileId,
+ DBLogEntry.playerId,
+ DBLogEntry.transactionId,
+ DBLogEntry.type,
+ DBItemEntry.mode,
+ DBItemEntry.itemId,
+ DBItemEntry.size)
+ .forEach { row ->
+ logs.getOrPut(row[DBLogEntry.transactionId]) {
+ LogEntry(row[DBLogEntry.type],
+ row[DBLogEntry.transactionId],
+ listOf())
+ }
+ items.getOrPut(row[DBLogEntry.transactionId]) { mutableListOf() }
+ .add(SerializableItemChange(
+ row[DBItemEntry.itemId].string,
+ row[DBItemEntry.mode],
+ row[DBItemEntry.size],
+ ))
}
- items.getOrPut(row[DBLogEntry.transactionId]) { mutableListOf() }
- .add(SerializableItemChange(
- row[DBItemEntry.itemId].string,
- row[DBItemEntry.mode],
- row[DBItemEntry.size],
- ))
- }
+ }
val compiled = logs.values.map { it.copy(items = items[it.id]!!) }
call.respond(compiled)
}.docs {
@@ -100,6 +161,12 @@ fun Route.apiRouting(database: Database) {
}
@Serializable
+data class AnalysisListing(
+ val name: String,
+ val id: String,
+)
+
+@Serializable
data class LogEntry(
val type: TransactionType,
val id: @Serializable(ULIDSerializer::class) ULIDWrapper,