Skip to content

Commit

Permalink
chore: event log pagination (#36)
Browse files Browse the repository at this point in the history
* chore: minor event log fixes
* chore: event-log rest-api + pagination
  • Loading branch information
mikeplotean authored Dec 13, 2023
1 parent edef4c3 commit 3a5a63d
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,5 @@ fun Application.module() {
accounts()
nfts()
issuers()
eventLogs()
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import java.net.URLDecoder
import kotlin.math.abs
import kotlin.time.Duration.Companion.seconds


Expand Down Expand Up @@ -435,7 +436,7 @@ class SSIKit2WalletService(tenant: String?, accountId: UUID, walletId: UUID) : W
else -> throw IllegalArgumentException("Invalid credential \"typ\": $typ")
}
logEvent(
EventType.Credential.Receive,
EventType.Credential.Accept,
parsedOfferReq.credentialOffer!!.credentialIssuer,
credentialResultPair.second
)
Expand Down Expand Up @@ -645,6 +646,25 @@ class SSIKit2WalletService(tenant: String?, accountId: UUID, walletId: UUID) : W
}
}

override fun filterEventLog(filter: EventLogFilter): EventLogFilterResult = runCatching {
val startingAfterItemIndex = filter.startingAfter?.toLongOrNull()?.takeIf { it >= 0 } ?: -1L
val dataFilter = emptyMap<String, String>()
val pageSize = filter.limit
val count = EventService.count(walletId, dataFilter)
val offset = startingAfterItemIndex + 1
val events = EventService.get(walletId, filter.limit, offset, dataFilter)
EventLogFilterDataResult(
items = events,
count = events.size,
currentStartingAfter = computeCurrentStartingAfter(startingAfterItemIndex, pageSize),
nextStartingAfter = computeNextStartingAfter(startingAfterItemIndex, pageSize, count)
)
}.fold(onSuccess = {
it
}, onFailure = {
EventLogFilterErrorResult(reason = it.localizedMessage)
})

override suspend fun linkWallet(wallet: WalletDataTransferObject): LinkedWalletDataTransferObject =
Web3WalletService.link(tenant, walletId, wallet)

Expand Down Expand Up @@ -709,4 +729,16 @@ class SSIKit2WalletService(tenant: String?, accountId: UUID, walletId: UUID) : W
policies = emptyList(),
protocol = "oid4vp",
)
}

//TODO: move to related entity
private fun computeCurrentStartingAfter(afterItemIndex: Long, pageSize: Int): String? = let {
val itemIndex = afterItemIndex - pageSize
itemIndex.takeIf { it >= -1 }?.toString()
}

//TODO: move to related entity
private fun computeNextStartingAfter(afterItemIndex: Long, pageSize: Int, count: Long): String? = let {
val itemIndex = afterItemIndex + pageSize
itemIndex.takeIf { it <= count }?.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import id.walt.webwallet.db.models.*
import id.walt.webwallet.service.dids.DidsService
import id.walt.webwallet.service.dto.LinkedWalletDataTransferObject
import id.walt.webwallet.service.dto.WalletDataTransferObject
import id.walt.webwallet.service.events.EventLogFilter
import id.walt.webwallet.service.events.EventLogFilterResult
import id.walt.webwallet.service.issuers.IssuerDataTransferObject
import id.walt.webwallet.utils.JsonUtils.toJsonPrimitive
import io.ktor.client.*
Expand Down Expand Up @@ -364,6 +366,10 @@ class WalletKitWalletService(tenant: String?, accountId: UUID, walletId: UUID) :
}
}

override fun filterEventLog(filter: EventLogFilter): EventLogFilterResult {
TODO("Not yet implemented")
}

override suspend fun linkWallet(wallet: WalletDataTransferObject): LinkedWalletDataTransferObject =
Web3WalletService.link(tenant, walletId, wallet)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import id.walt.webwallet.db.models.WalletDid
import id.walt.webwallet.db.models.WalletOperationHistory
import id.walt.webwallet.service.dto.LinkedWalletDataTransferObject
import id.walt.webwallet.service.dto.WalletDataTransferObject
import id.walt.webwallet.service.events.Event
import id.walt.webwallet.service.events.EventLogFilter
import id.walt.webwallet.service.events.EventLogFilterResult
import id.walt.webwallet.service.issuers.IssuerDataTransferObject
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
Expand Down Expand Up @@ -52,6 +53,9 @@ abstract class WalletService(val tenant: String?, val accountId: UUID, val walle
abstract fun getHistory(limit: Int = 10, offset: Long = 0): List<WalletOperationHistory>
abstract suspend fun addOperationHistory(operationHistory: WalletOperationHistory)

// EventLog
abstract fun filterEventLog(filter: EventLogFilter): EventLogFilterResult

// Web3 wallets
abstract suspend fun linkWallet(wallet: WalletDataTransferObject): LinkedWalletDataTransferObject
abstract suspend fun unlinkWallet(wallet: UUID): Boolean
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package id.walt.webwallet.service.events

data class EventLogFilter(
val limit: Int = 0,
val event: String? = null,
val action: String? = null,
val tenant: String? = null,
val startingAfter: String? = null,
val sortBy: String?=null,
val sortOrder: String?=null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package id.walt.webwallet.service.events

import kotlinx.serialization.Serializable

sealed class EventLogFilterResult

@Serializable
data class EventLogFilterDataResult(
val items: List<Event>,
val count: Int,
val currentStartingAfter: String? = null,
val nextStartingAfter: String? = null,
) : EventLogFilterResult()

@Serializable
data class EventLogFilterErrorResult(
val reason: String,
val message: String? = null,
) : EventLogFilterResult()
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
package id.walt.webwallet.service.events

import id.walt.webwallet.db.models.Events
import id.walt.webwallet.db.models.WalletOperationHistories
import kotlinx.datetime.toJavaInstant
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.uuid.UUID
import org.jetbrains.exposed.sql.Query
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction

object EventService {
fun get(walletId: UUID, limit: Int, offset: Long): List<Event> =
Events.select { WalletOperationHistories.wallet eq walletId }
.orderBy(WalletOperationHistories.timestamp)
.limit(n = limit, offset = offset).map {
fun get(walletId: UUID, limit: Int, offset: Long, dataFilter: Map<String, String> = emptyMap()): List<Event> =
addWhereClause(
Events.select { Events.wallet eq walletId }, dataFilter
).orderBy(Events.timestamp).limit(n = limit, offset = offset).map {
Event(it)
}

fun count(walletId: UUID, dataFilter: Map<String, String> = emptyMap()): Long = addWhereClause(
Events.select { Events.wallet eq walletId }, dataFilter
).count()


fun add(event: Event): Unit = transaction {
Events.insert {
it[tenant] = event.tenant ?: "global"
Expand All @@ -30,4 +36,20 @@ object EventService {
it[data] = Json.encodeToString(event.data)
}
}

private fun addWhereClause(query: Query, dataFilter: Map<String, String>) = let {
dataFilter["event"]?.let {
query.adjustWhere { Events.event eq it }
}
dataFilter["action"]?.let {
query.adjustWhere { Events.action eq it }
}
dataFilter["tenant"]?.let {
query.adjustWhere { Events.tenant eq it }
}
dataFilter["originator"]?.let {
query.adjustWhere { Events.originator eq it }
}
query
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package id.walt.webwallet.web.controllers

import id.walt.web.controllers.getWalletService
import id.walt.webwallet.service.events.EventLogFilter
import id.walt.webwallet.service.events.EventLogFilterResult
import io.github.smiley4.ktorswaggerui.dsl.get
import io.github.smiley4.ktorswaggerui.dsl.route
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.response.*
import org.jetbrains.exposed.sql.transactions.transaction

fun Application.eventLogs() = walletRoute {
route("eventlog", {
tags = listOf("Event Log")
}) {
get({
summary = "Retrieve event logs for currently signed in account wallet"
request {
queryParameter<String>("limit") {
description = "Page size"
example = "10"
required = false
}
queryParameter<String>("event") {
description = "Event type"
example = "Account"
required = false
}
queryParameter<String>("action") {
description = "Action type"
example = "Login"
required = false
}
queryParameter<String>("tenant") {
description = "Tenant"
example = "global"
required = false
}
queryParameter<String>("startingAfter") {
description = "Starting after page"
example = "<hash>"
required = false
}
queryParameter<String>("sortBy") {
description = "The property to sort by"
example = "tenant"
required = false
}
queryParameter<String>("sortOrder") {
description = "The sort order [ASC|DESC]"
example = "ASC"
required = false
}
}
response {
HttpStatusCode.OK to {
body<EventLogFilterResult> {
description = "The event log result"
}
}
}
}) {
val wallet = getWalletService()
val limit = call.request.queryParameters["limit"]?.toIntOrNull() ?: 0
val event = call.request.queryParameters["event"]
val action = call.request.queryParameters["action"]
val tenant = call.request.queryParameters["tenant"]
val startingAfter = call.request.queryParameters["startingAfter"]
val sortBy = call.request.queryParameters["sortBy"]
val sortOrder = call.request.queryParameters["sortOrder"]
context.respond(transaction {
wallet.filterEventLog(
EventLogFilter(
limit = limit,
event = event,
action = action,
tenant = tenant,
startingAfter = startingAfter,
sortBy = sortBy,
sortOrder = sortOrder,
)
)
})
}
}
}

0 comments on commit 3a5a63d

Please sign in to comment.