From f67e362be295c70eb79754a9d7a1e21d5580a816 Mon Sep 17 00:00:00 2001 From: lzray-universe <87802707+lzray-universe@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:32:23 +0800 Subject: [PATCH 1/3] Refactor getUserChats to improve query efficiency --- src/main/kotlin/database/ChatMembers.kt | 145 +++++++++++++++--------- 1 file changed, 89 insertions(+), 56 deletions(-) diff --git a/src/main/kotlin/database/ChatMembers.kt b/src/main/kotlin/database/ChatMembers.kt index fa15ab6..905f264 100644 --- a/src/main/kotlin/database/ChatMembers.kt +++ b/src/main/kotlin/database/ChatMembers.kt @@ -59,63 +59,96 @@ class ChatMembers: SqlDao(ChatMemberTable) suspend fun getUserChats(userId: UserId): List = query() { - val myMemberships = table.selectAll().where { table.user eq userId }.toList() - - val chats = myMemberships.mapNotNull() - { row -> - val chatId = row[table.chat].value - val chatRow = chats.table.selectAll().where { chats.table.id eq chatId }.singleOrNull() - - // Filter out moment chats from regular chat list - val isMoment = chatRow?.get(chats.table.isMoment) ?: false - if (isMoment) return@mapNotNull null - - val isPrivate = chatRow?.get(chats.table.private) ?: false - val chatName = chatRow?.get(chats.table.name) - val burnTime = chatRow?.get(chats.table.burnTime) - val myKey = row[table.key] - val otherMembersInfo = table.selectAll().where { (table.chat eq chatId) and (table.user neq userId) } - .map() - { mRow -> - val uid = mRow[table.user].value - val userRow = users.table.selectAll().where { users.table.id eq uid }.single() - val uname = userRow[users.table.username] - val isDonor = userRow[users.table.donationAmount] > 0 - Triple(uid, uname, isDonor) - } - - val parsedOtherNames = otherMembersInfo.map { it.second } - val parsedOtherIds = otherMembersInfo.map { it.first.value } - val otherUserIsDonor = otherMembersInfo.firstOrNull()?.third ?: false - - // For private chats, use the other person's name instead of chat name - val displayName = if (isPrivate && parsedOtherNames.isNotEmpty()) - { - parsedOtherNames.joinToString(", ") - } - else - { - chatName ?: parsedOtherNames.joinToString(", ") - } - - ChatMember( - chatId = chatId, - name = displayName, - key = myKey, - parsedOtherNames = parsedOtherNames, - parsedOtherIds = parsedOtherIds, - isPrivate = isPrivate, - unreadCount = row[table.unread], - doNotDisturb = row[table.doNotDisturb], - burnTime = burnTime, - otherUserIsDonor = otherUserIsDonor + val cm = table + val chatTable = chats.table + val userTable = users.table + + + val membershipRows = (cm innerJoin chatTable) + .select( + cm.chat, + cm.key, + cm.unread, + cm.doNotDisturb, + chatTable.id, + chatTable.name, + chatTable.private, + chatTable.burnTime, + chatTable.lastChatAt, ) - } - val chatTable = get().table - val times = chatTable.select(chatTable.id, chatTable.lastChatAt).where { chatTable.id inList chats.map { it.chatId } } - .associate { it[chatTable.id].value to it[chatTable.lastChatAt].toEpochMilliseconds() } - chats.sortedByDescending { times[it.chatId] ?: 0L } + .where { (cm.user eq userId) and (chatTable.isMoment eq false) } + .toList() + + if (membershipRows.isEmpty()) return@query emptyList() + + val chatIds = membershipRows.map { it[chatTable.id].value } + + + val othersByChat: Map>> = + (cm innerJoin userTable) + .select( + cm.chat, + userTable.id, + userTable.username, + userTable.donationAmount, + ) + .where { (cm.chat inList chatIds) and (cm.user neq userId) } + .toList() + .groupBy( + keySelector = { row -> row[cm.chat].value }, + valueTransform = { row -> + val uid = row[userTable.id].value + val uname = row[userTable.username] + val isDonor = row[userTable.donationAmount] > 0 + Triple(uid, uname, isDonor) + } + ) + + + membershipRows + .map { row -> + val chatId = row[chatTable.id].value + val isPrivate = row[chatTable.private] + val chatName = row[chatTable.name] + val burnTime = row[chatTable.burnTime] + val lastAt = row[chatTable.lastChatAt].toEpochMilliseconds() + + val myKey = row[cm.key] + val others = othersByChat[chatId].orEmpty() + + val parsedOtherNames = others.map { it.second } + val parsedOtherIds = others.map { it.first.value } + + + val otherUserIsDonor = if (isPrivate && others.size == 1) others[0].third else false + + val displayName = + if (isPrivate && parsedOtherNames.isNotEmpty()) + { + parsedOtherNames.joinToString(", ") + } + else + { + chatName.ifBlank { parsedOtherNames.joinToString(", ") } + } + + ChatMember( + chatId = chatId, + name = displayName, + key = myKey, + parsedOtherNames = parsedOtherNames, + parsedOtherIds = parsedOtherIds, + isPrivate = isPrivate, + unreadCount = row[cm.unread], + doNotDisturb = row[cm.doNotDisturb], + burnTime = burnTime, + otherUserIsDonor = otherUserIsDonor + ) to lastAt + } + .sortedByDescending { it.second } + .map { it.first } } + suspend fun getMemberIds(chatId: ChatId): List = query() { @@ -207,4 +240,4 @@ class ChatMembers: SqlDao(ChatMemberTable) .where { (table.chat eq chatId) and (table.user eq userId) } .singleOrNull()?.get(table.key) } -} \ No newline at end of file +} From 321ac7146f54f28b8cb93d6b67a76e09e5ee9ecc Mon Sep 17 00:00:00 2001 From: lzray-universe <87802707+lzray-universe@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:42:19 +0800 Subject: [PATCH 2/3] Refactor UserRoute to use query parameters Updated parameter retrieval to use query parameters and improved error responses. --- src/main/kotlin/route/UserRoute.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/route/UserRoute.kt b/src/main/kotlin/route/UserRoute.kt index 27df4f6..194bfa1 100644 --- a/src/main/kotlin/route/UserRoute.kt +++ b/src/main/kotlin/route/UserRoute.kt @@ -108,9 +108,13 @@ fun Route.userRoute() get("/publicKey") { - val username = call.parameters["username"] ?: return@get call.respond(HttpStatusCode.BadRequest) - val user = getKoin().get().getUserByUsername(username) ?: return@get call.respond(HttpStatusCode.NotFound) - val response = buildJsonObject() + val username = call.request.queryParameters["username"] + ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing username") + + val user = getKoin().get().getUserByUsername(username) + ?: return@get call.respond(HttpStatusCode.NotFound) + + val response = buildJsonObject { put("publicKey", user.publicKey) } @@ -119,24 +123,23 @@ fun Route.userRoute() get("/info") { - val idStr = call.parameters["id"] + val idStr = call.request.queryParameters["id"] val id = idStr?.toIntOrNull() if (id == null) { - call.respond(HttpStatusCode.BadRequest) + call.respond(HttpStatusCode.BadRequest, "Missing or invalid id") return@get } val user = getKoin().get().getUser(UserId(id)) - if (user == null) { call.respond(HttpStatusCode.NotFound) return@get } - val response = buildJsonObject() + val response = buildJsonObject { put("id", user.id.value) put("username", user.username) From 4c8a41acb845e906b53d0850a1eb361488791321 Mon Sep 17 00:00:00 2001 From: lzray-universe <87802707+lzray-universe@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:44:07 +0800 Subject: [PATCH 3/3] Swap order of configs in mergeConfigs call --- src/main/kotlin/Main.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index bdf73e6..5dfd36d 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -103,7 +103,7 @@ fun main(args: Array) } val defaultConfig = Loader.getResource("application.yaml") ?: error("application.yaml not found") val customConfig = configFile.inputStream() - val resConfig = Loader.mergeConfigs(defaultConfig, customConfig) + val resConfig = Loader.mergeConfigs(customConfig, defaultConfig) val tempFile = File.createTempFile("resConfig", ".yaml") tempFile.writeText(Yaml.default.encodeToString(resConfig)) val resArgs = args1 + "-config=${tempFile.absolutePath}" @@ -144,4 +144,4 @@ fun Application.init() // router router() -} \ No newline at end of file +}