aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-01-21 00:10:18 +0100
committerLinnea Gräf <nea@nea.moe>2025-01-21 00:15:49 +0100
commit4e20d0e95b2fe6ef71a33f41f9cd8cebd06cf982 (patch)
treefd681d3ff3ba3796d443b408e8f25b6c7ec4f509
parentbf90a9b8eb85f28fbd2285aaa26316faf9753eb2 (diff)
downloadLocalTransactionLedger-4e20d0e95b2fe6ef71a33f41f9cd8cebd06cf982.tar.gz
LocalTransactionLedger-4e20d0e95b2fe6ef71a33f41f9cd8cebd06cf982.tar.bz2
LocalTransactionLedger-4e20d0e95b2fe6ef71a33f41f9cd8cebd06cf982.zip
feat(server): Add example request route
-rw-r--r--server/core/src/main/kotlin/moe/nea/ledger/server/core/api/BaseApi.kt75
-rw-r--r--server/core/test-requests/test-hello.http5
-rw-r--r--server/frontend/src/api-schema.d.ts198
3 files changed, 196 insertions, 82 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 c58763f..4bc6472 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
@@ -8,12 +8,23 @@ import io.ktor.server.routing.get
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
+import moe.nea.ledger.ItemChange
+import moe.nea.ledger.TransactionType
+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 sh.ondr.jsonschema.jsonSchema
+import moe.nea.ledger.utils.ULIDWrapper
fun Route.apiRouting(database: Database) {
get("/profiles") {
@@ -51,12 +62,74 @@ fun Route.apiRouting(database: Database) {
schema<Map<String, String?>>()
}
}
+ 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())
+ }
+ 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 {
+ summary = "Get all log entries"
+ operationId = "getLogEntries"
+ tag(Tags.DATA)
+ respondsOk {
+ schema<List<LogEntry>>()
+ }
+ }
+}
+
+@Serializable
+data class LogEntry(
+ val type: TransactionType,
+ val id: @Serializable(ULIDSerializer::class) ULIDWrapper,
+ val items: List<SerializableItemChange>,
+)
+
+@Serializable
+data class SerializableItemChange(
+ val itemId: String,
+ val direction: ItemChange.ChangeDirection,
+ val amount: Double,
+)
+
+object ULIDSerializer : KSerializer<ULIDWrapper> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ULID", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): ULIDWrapper {
+ return ULIDWrapper(decoder.decodeString())
+ }
+
+ override fun serialize(encoder: Encoder, value: ULIDWrapper) {
+ encoder.encodeString(value.wrapped)
+ }
}
enum class Tags : IntoTag {
PROFILE,
HYPIXEL,
MANAGEMENT,
+ DATA,
;
override fun intoTag(): String {
diff --git a/server/core/test-requests/test-hello.http b/server/core/test-requests/test-hello.http
deleted file mode 100644
index 3ddf352..0000000
--- a/server/core/test-requests/test-hello.http
+++ /dev/null
@@ -1,5 +0,0 @@
-### GET request to example server
-GET localhost:8080/
-
-### GET profiles
-GET localhost:8080/api/profiles \ No newline at end of file
diff --git a/server/frontend/src/api-schema.d.ts b/server/frontend/src/api-schema.d.ts
index 93bedc9..eab0d7e 100644
--- a/server/frontend/src/api-schema.d.ts
+++ b/server/frontend/src/api-schema.d.ts
@@ -4,95 +4,141 @@
*/
export interface paths {
- "/profiles": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ "/profiles": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** List all profiles and players known to ledger */
+ get: operations["listProfiles"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/item": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get item names for item ids */
+ get: operations["getItemNames"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
};
- /** List all profiles and players known to ledger */
- get: operations["listProfiles"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
- };
- "/item": {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
+ "/entries": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get all log entries */
+ get: operations["getLogEntries"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
};
- /** Get item names for item ids */
- get: operations["getItemNames"];
- put?: never;
- post?: never;
- delete?: never;
- options?: never;
- head?: never;
- patch?: never;
- trace?: never;
- };
}
export type webhooks = Record<string, never>;
export interface components {
- schemas: never;
- responses: never;
- parameters: never;
- requestBodies: never;
- headers: never;
- pathItems: never;
+ schemas: never;
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
- listProfiles: {
- parameters: {
- query?: never;
- header?: never;
- path?: never;
- cookie?: never;
- };
- requestBody?: never;
- responses: {
- 200: {
- headers: {
- [name: string]: unknown;
+ listProfiles: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- content: {
- "application/json": {
- playerId: string;
- profileId: string;
- }[];
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ playerId: string;
+ profileId: string;
+ }[];
+ };
+ };
};
- };
};
- };
- getItemNames: {
- parameters: {
- query?: {
- itemId?: string[];
- };
- header?: never;
- path?: never;
- cookie?: never;
+ getItemNames: {
+ parameters: {
+ query?: {
+ itemId?: string[];
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ [key: string]: string;
+ };
+ };
+ };
+ };
};
- requestBody?: never;
- responses: {
- 200: {
- headers: {
- [name: string]: unknown;
+ getLogEntries: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
- content: {
- "application/json": {
- [key: string]: string;
- };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ /** @enum {string} */
+ type: "ACCESSORIES_SWAPPING" | "ALLOWANCE_GAIN" | "AUCTION_BOUGHT" | "AUCTION_LISTING_CHARGE" | "AUCTION_SOLD" | "AUTOMERCHANT_PROFIT_COLLECT" | "BANK_DEPOSIT" | "BANK_WITHDRAW" | "BAZAAR_BUY_INSTANT" | "BAZAAR_BUY_ORDER" | "BAZAAR_SELL_INSTANT" | "BAZAAR_SELL_ORDER" | "BITS_PURSE_STATUS" | "BOOSTER_COOKIE_ATE" | "CAPSAICIN_EYEDROPS_USED" | "COMMUNITY_SHOP_BUY" | "CORPSE_DESECRATED" | "DIE_ROLLED" | "DRACONIC_SACRIFICE" | "DUNGEON_CHEST_OPEN" | "FORGED" | "GOD_POTION_DRANK" | "GOD_POTION_MIXIN_DRANK" | "GUMMY_POLAR_BEAR_ATE" | "KAT_TIMESKIP" | "KAT_UPGRADE" | "KISMET_REROLL" | "KUUDRA_CHEST_OPEN" | "NPC_BUY" | "NPC_SELL" | "PEST_REPELLENT_USED" | "VISITOR_BARGAIN" | "WYRM_EVOKED";
+ id: string;
+ items: {
+ itemId: string;
+ /** @enum {string} */
+ direction: "GAINED" | "TRANSFORM" | "SYNC" | "CATALYST" | "LOST";
+ amount: number;
+ }[];
+ }[];
+ };
+ };
};
- };
};
- };
}