Skip to content

Commit 08a0da5

Browse files
committed
fix: resolve history avatars from subscriptions
1 parent 3acf040 commit 08a0da5

2 files changed

Lines changed: 62 additions & 11 deletions

File tree

‎server-services/src/main/kotlin/dev/typetype/server/services/HistoryService.kt‎

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ package dev.typetype.server.services
33
import dev.typetype.server.db.DatabaseFactory
44
import dev.typetype.server.db.tables.HistoryTable
55
import dev.typetype.server.db.tables.ProgressTable
6+
import dev.typetype.server.db.tables.SubscriptionsTable
67
import dev.typetype.server.models.HistoryItem
78
import org.jetbrains.exposed.v1.core.LowerCase
89
import org.jetbrains.exposed.v1.core.SortOrder
910
import org.jetbrains.exposed.v1.core.and
1011
import org.jetbrains.exposed.v1.core.eq
1112
import org.jetbrains.exposed.v1.core.greaterEq
13+
import org.jetbrains.exposed.v1.core.inList
1214
import org.jetbrains.exposed.v1.core.less
1315
import org.jetbrains.exposed.v1.core.like
16+
import org.jetbrains.exposed.v1.core.neq
1417
import org.jetbrains.exposed.v1.core.or
1518
import org.jetbrains.exposed.v1.jdbc.andWhere
1619
import org.jetbrains.exposed.v1.jdbc.batchInsert
@@ -26,20 +29,49 @@ class HistoryService {
2629
.orderBy(HistoryTable.watchedAt to SortOrder.DESC, HistoryTable.id to SortOrder.DESC)
2730
.toList()
2831
HistoryProgressMapper.toHistoryItemsForExport(userId, rows)
32+
}.withSubscriptionAvatars(userId)
33+
34+
suspend fun search(userId: String, q: String?, from: Long?, to: Long?, limit: Int, offset: Int): Pair<List<HistoryItem>, Long> {
35+
val (items, total) = DatabaseFactory.query {
36+
val query = HistoryTable.selectAll().where { HistoryTable.userId eq userId }
37+
if (!q.isNullOrBlank()) {
38+
val pattern = "%${q.lowercase()}%"
39+
query.andWhere { (LowerCase(HistoryTable.title) like pattern) or (LowerCase(HistoryTable.channelName) like pattern) }
40+
}
41+
if (from != null) query.andWhere { HistoryTable.watchedAt greaterEq from }
42+
if (to != null) query.andWhere { HistoryTable.watchedAt less to }
43+
val total = query.count()
44+
val rows = query.orderBy(HistoryTable.watchedAt to SortOrder.DESC, HistoryTable.id to SortOrder.DESC).limit(limit).offset(offset.toLong()).toList()
45+
HistoryProgressMapper.toHistoryItems(userId, rows) to total
46+
}
47+
return items.withSubscriptionAvatars(userId) to total
2948
}
3049

31-
suspend fun search(userId: String, q: String?, from: Long?, to: Long?, limit: Int, offset: Int): Pair<List<HistoryItem>, Long> = DatabaseFactory.query {
32-
val query = HistoryTable.selectAll().where { HistoryTable.userId eq userId }
33-
if (!q.isNullOrBlank()) {
34-
val pattern = "%${q.lowercase()}%"
35-
query.andWhere { (LowerCase(HistoryTable.title) like pattern) or (LowerCase(HistoryTable.channelName) like pattern) }
50+
private suspend fun List<HistoryItem>.withSubscriptionAvatars(userId: String): List<HistoryItem> {
51+
val missingUrls = filter { it.channelAvatar.isBlank() && it.channelUrl.isNotBlank() }
52+
.map { ChannelUrlCanonicalizer.canonicalize(it.channelUrl) }
53+
.distinct()
54+
if (missingUrls.isEmpty()) return this
55+
val avatars = DatabaseFactory.query {
56+
SubscriptionsTable.selectAll()
57+
.where {
58+
(SubscriptionsTable.userId eq userId) and
59+
(SubscriptionsTable.channelUrl inList missingUrls) and
60+
(SubscriptionsTable.avatarUrl neq "")
61+
}
62+
.map {
63+
ChannelUrlCanonicalizer.canonicalize(it[SubscriptionsTable.channelUrl]) to
64+
it[SubscriptionsTable.avatarUrl]
65+
}
66+
.toMap()
67+
}
68+
if (avatars.isEmpty()) return this
69+
return map { item ->
70+
if (item.channelAvatar.isNotBlank()) item
71+
else avatars[ChannelUrlCanonicalizer.canonicalize(item.channelUrl)]?.let {
72+
item.copy(channelAvatar = it)
73+
} ?: item
3674
}
37-
if (from != null) query.andWhere { HistoryTable.watchedAt greaterEq from }
38-
if (to != null) query.andWhere { HistoryTable.watchedAt less to }
39-
val total = query.count()
40-
val rows = query.orderBy(HistoryTable.watchedAt to SortOrder.DESC, HistoryTable.id to SortOrder.DESC).limit(limit).offset(offset.toLong()).toList()
41-
val items = HistoryProgressMapper.toHistoryItems(userId, rows)
42-
items to total
4375
}
4476

4577
suspend fun add(userId: String, item: HistoryItem): HistoryItem = insert(userId, item, System.currentTimeMillis())

‎src/test/kotlin/dev/typetype/server/SubscriptionsAvatarRepairServiceTest.kt‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import dev.typetype.server.services.SubscriptionGroupWriteResult
1212
import dev.typetype.server.services.SubscriptionGroupsService
1313
import dev.typetype.server.services.SubscriptionSelection
1414
import dev.typetype.server.services.SubscriptionsService
15+
import dev.typetype.server.services.HistoryService
1516
import kotlinx.coroutines.test.runTest
1617
import org.jetbrains.exposed.v1.core.and
1718
import org.jetbrains.exposed.v1.core.eq
@@ -127,6 +128,24 @@ class SubscriptionsAvatarRepairServiceTest {
127128
assertEquals(WATCH_AVATAR_URL, storedFavoriteAvatar())
128129
}
129130

131+
@Test
132+
fun `history reads subscription avatars without waiting for a repair write`() = runTest {
133+
DatabaseFactory.query {
134+
SubscriptionsTable.insert {
135+
it[userId] = TEST_USER_ID
136+
it[channelUrl] = WATCH_CHANNEL_URL
137+
it[name] = "Channel"
138+
it[avatarUrl] = WATCH_AVATAR_URL
139+
it[subscribedAt] = 1L
140+
}
141+
}
142+
addHistory(channelUrl = WATCH_CHANNEL_URL, avatarUrl = "", watchedAt = 1L)
143+
144+
val item = HistoryService().search(TEST_USER_ID, null, null, null, 1, 0).first.single()
145+
146+
assertEquals(WATCH_AVATAR_URL, item.channelAvatar)
147+
}
148+
130149
private suspend fun addSubscription(channelUrl: String): Unit {
131150
service.add(TEST_USER_ID, SubscriptionItem(channelUrl = channelUrl, name = "Channel", avatarUrl = ""))
132151
}

0 commit comments

Comments
 (0)