Show complete user credit data in admin

Load users by registration order, expose consumed and current credits, and accept short internal IDs so support can reliably locate accounts.
This commit is contained in:
Rocky
2026-08-19 17:54:27 +08:00
parent c592f426be
commit 58445dd880
9 changed files with 195 additions and 31 deletions
@@ -143,6 +143,40 @@ class AdminUsersServiceTest : FunSpec({
}
}
test("search accepts an uppercase 8-character internal user ID suffix") {
val matchingId = UUID.fromString("11111111-1111-4111-8111-aaaae3c7c475")
val otherId = UUID.fromString("22222222-2222-4222-8222-bbbb12345678")
val service = AdminUsersService(
PagingUsersRepository(
listOf(
summary(matchingId, Instant.parse("2026-08-15T02:00:00Z")),
summary(otherId, Instant.parse("2026-08-15T01:00:00Z")),
),
),
)
val page = service.searchByInternalId("E3C7C475")
page.items.map { it.id } shouldBe listOf(matchingId.toString())
page.nextCursor shouldBe null
}
test("short ID search returns all collisions for disambiguation") {
val firstId = UUID.fromString("11111111-1111-4111-8111-aaaae3c7c475")
val secondId = UUID.fromString("22222222-2222-4222-8222-bbbbe3c7c475")
val service = AdminUsersService(
PagingUsersRepository(
listOf(
summary(firstId, Instant.parse("2026-08-15T02:00:00Z")),
summary(secondId, Instant.parse("2026-08-15T01:00:00Z")),
),
),
)
service.searchByInternalId("e3c7c475").items.map { it.id } shouldBe
listOf(firstId.toString(), secondId.toString())
}
test("invalid user cursor and missing detail fail without returning user data") {
val service = AdminUsersService(PagingUsersRepository(emptyList()))
@@ -178,6 +212,12 @@ private class PagingUsersRepository(
ledgerLimit: Int,
): AdminUserDetailDto? = details[userId]
override suspend fun findByIdSuffix(
suffix: String,
limit: Int,
): List<AdminUserSummaryDto> =
users.filter { it.id.endsWith(suffix, ignoreCase = true) }.take(limit)
override suspend fun exists(userId: UUID): Boolean =
userId in details || userId in ledger || users.any { it.id == userId.toString() }